diff --git a/sys/dev/cx/if_cx.c b/sys/dev/cx/if_cx.c index 386d418ce159..040d0c543a36 100644 --- a/sys/dev/cx/if_cx.c +++ b/sys/dev/cx/if_cx.c @@ -1,3278 +1,3284 @@ /* * Cronyx-Sigma adapter driver for FreeBSD. * Supports PPP/HDLC and Cisco/HDLC protocol in synchronous mode, * and asyncronous channels with full modem control. * Keepalive protocol implemented in both Cisco and PPP modes. * * Copyright (C) 1994-2002 Cronyx Engineering. * Author: Serge Vakulenko, * * Copyright (C) 1999-2004 Cronyx Engineering. * Rewritten on DDK, ported to NETGRAPH, rewritten for FreeBSD 3.x-5.x by * Kurakin Roman, * * This software is distributed with NO WARRANTIES, not even the implied * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * Authors grant any other persons or organisations a permission to use, * modify and redistribute this software in source and binary forms, * as long as this message is kept with the software, all derivative * works or modified versions. * * Cronyx Id: if_cx.c,v 1.1.2.23 2004/02/26 17:56:40 rik Exp $ */ #include __FBSDID("$FreeBSD$"); #include #if __FreeBSD_version >= 500000 # define NCX 1 #else # include "cx.h" #endif #if NCX > 0 #include #include #include #include #include #include #include #include #include #include #include #if __FreeBSD_version >= 400000 # include # include # include # include #endif #include #include #include #include #include #include #include #include #if __FreeBSD_version < 500000 #include #include #endif #if __FreeBSD_version >= 400000 # include # if __FreeBSD_version <= 501000 # include # endif #endif #if __FreeBSD_version >= 400000 # include # include # include #else # include # include # include #endif #include "opt_ng_cronyx.h" #ifdef NETGRAPH_CRONYX # include "opt_netgraph.h" # include # include # if __FreeBSD_version >= 400000 # include # else # include # endif #else # include # if __FreeBSD_version < 500000 # include "sppp.h" # if NSPPP <= 0 # error The device cx requires sppp or netgraph. # endif # endif # include # define PP_CISCO IFF_LINK2 #if __FreeBSD_version < 400000 # include # if NBPFILTER > 0 # include # endif #else # if __FreeBSD_version < 500000 # include # endif # include # define NBPFILTER NBPF #endif #endif #if __FreeBSD_version < 502113 #define ttyld_modem(foo, bar) ((*linesw[(foo)->t_line].l_modem)((foo), (bar))) #define ttyld_rint(foo, bar) ((*linesw[(foo)->t_line].l_rint)((bar), (foo))) #define ttyld_start(foo) ((*linesw[(foo)->t_line].l_start)((foo))) #define ttyld_open(foo, bar) ((*linesw[(foo)->t_line].l_open) ((bar), (foo))) #define ttyld_close(foo, bar) ((*linesw[(foo)->t_line].l_close) ((foo), (bar))) #define ttyld_read(foo, bar, barf) ((*linesw[(foo)->t_line].l_read) ((foo), (bar), (barf))) #define ttyld_write(foo, bar, barf) ((*linesw[(foo)->t_line].l_write) ((foo), (bar), (barf))) #endif /* If we don't have Cronyx's sppp version, we don't have fr support via sppp */ #ifndef PP_FR #define PP_FR 0 #endif #define CX_DEBUG(d,s) ({if (d->chan->debug) {\ printf ("%s: ", d->name); printf s;}}) #define CX_DEBUG2(d,s) ({if (d->chan->debug>1) {\ printf ("%s: ", d->name); printf s;}}) #define UNIT(d) (minor(d) & 0x3f) #define IF_CUNIT(d) (minor(d) & 0x40) #define UNIT_CTL 0x3f #define CALLOUT(d) (minor(d) & 0x80) #define CDEV_MAJOR 42 typedef struct _async_q { int beg; int end; #define BF_SZ 14400 int buf[BF_SZ+1]; } async_q; #define AQ_GSZ(q) ((BF_SZ + (q)->end - (q)->beg)%BF_SZ) #define AQ_PUSH(q,c) {*((q)->buf + (q)->end) = c;\ (q)->end = ((q)->end + 1)%BF_SZ;} #define AQ_POP(q,c) {c = *((q)->buf + (q)->beg);\ (q)->beg = ((q)->beg + 1)%BF_SZ;} #if __FreeBSD_version >= 400000 static void cx_identify __P((driver_t *, device_t)); static int cx_probe __P((device_t)); static int cx_attach __P((device_t)); static int cx_detach __P((device_t)); static device_method_t cx_isa_methods [] = { DEVMETHOD(device_identify, cx_identify), DEVMETHOD(device_probe, cx_probe), DEVMETHOD(device_attach, cx_attach), DEVMETHOD(device_detach, cx_detach), {0, 0} }; typedef struct _bdrv_t { cx_board_t *board; struct resource *base_res; struct resource *drq_res; struct resource *irq_res; int base_rid; int drq_rid; int irq_rid; void *intrhand; } bdrv_t; static driver_t cx_isa_driver = { "cx", cx_isa_methods, sizeof (bdrv_t), }; static devclass_t cx_devclass; #endif typedef struct _drv_t { char name [8]; cx_chan_t *chan; cx_board_t *board; cx_buf_t buf; struct tty tty; struct callout_handle dcd_timeout_handle; unsigned dtrwait; unsigned dtroff; unsigned callout; unsigned lock; int open_dev; int cd; int running; struct callout_handle dtr_timeout_handle; #ifdef NETGRAPH char nodename [NG_NODELEN+1]; hook_p hook; hook_p debug_hook; node_p node; struct ifqueue lo_queue; struct ifqueue hi_queue; short timeout; struct callout_handle timeout_handle; #else struct sppp pp; #endif #if __FreeBSD_version >= 400000 dev_t devt[3]; #endif async_q aqueue; #define CX_READ 1 #define CX_WRITE 2 int intr_action; short atimeout; } drv_t; extern long csigma_fw_len; extern const char *csigma_fw_version; extern const char *csigma_fw_date; extern const char *csigma_fw_copyright; extern const cr_dat_tst_t csigma_fw_tvec[]; extern const u_char csigma_fw_data[]; static void cx_oproc (struct tty *tp); static int cx_param (struct tty *tp, struct termios *t); static void cx_stop (struct tty *tp, int flag); static void cx_dtrwakeup (void *a); static void cx_receive (cx_chan_t *c, char *data, int len); static void cx_transmit (cx_chan_t *c, void *attachment, int len); static void cx_error (cx_chan_t *c, int data); static void cx_modem (cx_chan_t *c); static void cx_up (drv_t *d); static void cx_start (drv_t *d); -static void disc_optim(struct tty *tp, struct termios *t); +#if __FreeBSD_version < 502113 +static void ttyldoptim(struct tty *tp); +#endif #if __FreeBSD_version < 500000 static swihand_t cx_softintr; #else static void cx_softintr (void *); static void *cx_fast_ih; #endif static void cx_down (drv_t *d); static void cx_watchdog (drv_t *d); static void cx_carrier (void *arg); #ifdef NETGRAPH extern struct ng_type typestruct; #else static void cx_ifstart (struct ifnet *ifp); static void cx_tlf (struct sppp *sp); static void cx_tls (struct sppp *sp); static void cx_ifwatchdog (struct ifnet *ifp); static int cx_sioctl (struct ifnet *ifp, u_long cmd, caddr_t data); static void cx_initialize (void *softc); #endif static cx_board_t *adapter [NCX]; static drv_t *channel [NCX*NCHAN]; static struct callout_handle led_timo [NCX]; static struct callout_handle timeout_handle; #if __FreeBSD_version >= 400000 extern struct cdevsw cx_cdevsw; #endif static int MY_SOFT_INTR; /* * Print the mbuf chain, for debug purposes only. */ static void printmbuf (struct mbuf *m) { printf ("mbuf:"); for (; m; m=m->m_next) { if (m->m_flags & M_PKTHDR) printf (" HDR %d:", m->m_pkthdr.len); if (m->m_flags & M_EXT) printf (" EXT:"); printf (" %d", m->m_len); } printf ("\n"); } /* * Make an mbuf from data. */ static struct mbuf *makembuf (void *buf, u_int len) { struct mbuf *m, *o, *p; MGETHDR (m, M_DONTWAIT, MT_DATA); if (! m) return 0; if (len >= MINCLSIZE) MCLGET (m, M_DONTWAIT); m->m_pkthdr.len = len; m->m_len = 0; p = m; while (len) { u_int n = M_TRAILINGSPACE (p); if (n > len) n = len; if (! n) { /* Allocate new mbuf. */ o = p; MGET (p, M_DONTWAIT, MT_DATA); if (! p) { m_freem (m); return 0; } if (len >= MINCLSIZE) MCLGET (p, M_DONTWAIT); p->m_len = 0; o->m_next = p; n = M_TRAILINGSPACE (p); if (n > len) n = len; } bcopy (buf, mtod (p, caddr_t) + p->m_len, n); p->m_len += n; buf = n + (char*) buf; len -= n; } return m; } /* * Recover after lost transmit interrupts. */ static void cx_timeout (void *arg) { drv_t *d; int s, i; for (i=0; iatimeout == 1 && d->tty.t_state & TS_BUSY) { d->tty.t_state &= ~TS_BUSY; if (d->tty.t_dev) { d->intr_action |= CX_WRITE; MY_SOFT_INTR = 1; #if __FreeBSD_version >= 500000 swi_sched (cx_fast_ih, 0); #else setsofttty (); #endif } CX_DEBUG (d, ("cx_timeout\n")); } if (d->atimeout) d->atimeout--; splx (s); } timeout_handle = timeout (cx_timeout, 0, hz*5); } static void cx_led_off (void *arg) { cx_board_t *b = arg; int s = splhigh (); cx_led (b, 0); led_timo[b->num].callout = 0; splx (s); } /* * Activate interupt handler from DDK. */ #if __FreeBSD_version >= 400000 static void cx_intr (void *arg) { bdrv_t *bd = arg; cx_board_t *b = bd->board; #else static void cx_intr (int bnum) { cx_board_t *b = adapter [bnum]; #endif int s = splhigh (); /* Turn LED on. */ cx_led (b, 1); cx_int_handler (b); /* Turn LED off 50 msec later. */ if (! led_timo[b->num].callout) led_timo[b->num] = timeout (cx_led_off, b, hz/20); splx (s); } static int probe_irq (cx_board_t *b, int irq) { int mask, busy, cnt; /* Clear pending irq, if any. */ cx_probe_irq (b, -irq); DELAY (100); for (cnt=0; cnt<5; ++cnt) { /* Get the mask of pending irqs, assuming they are busy. * Activate the adapter on given irq. */ busy = cx_probe_irq (b, irq); DELAY (100); /* Get the mask of active irqs. * Deactivate our irq. */ mask = cx_probe_irq (b, -irq); DELAY (100); if ((mask & ~busy) == 1 << irq) { cx_probe_irq (b, 0); /* printf ("cx%d: irq %d ok, mask=0x%04x, busy=0x%04x\n", b->num, irq, mask, busy); */ return 1; } } /* printf ("cx%d: irq %d not functional, mask=0x%04x, busy=0x%04x\n", b->num, irq, mask, busy); */ cx_probe_irq (b, 0); return 0; } static short porttab [] = { 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0x300, 0x320, 0x340, 0x360, 0x380, 0x3a0, 0x3c0, 0x3e0, 0 }; static char dmatab [] = { 7, 6, 5, 0 }; static char irqtab [] = { 5, 10, 11, 7, 3, 15, 12, 0 }; #if __FreeBSD_version >= 400000 static int cx_is_free_res (device_t dev, int rid, int type, u_long start, u_long end, u_long count) { struct resource *res; if (!(res = bus_alloc_resource (dev, type, &rid, start, end, count, RF_ALLOCATED))) return 0; bus_release_resource (dev, type, rid, res); return 1; } static void cx_identify (driver_t *driver, device_t dev) { u_long iobase, rescount; int devcount; device_t *devices; device_t child; devclass_t my_devclass; int i, k; if ((my_devclass = devclass_find ("cx")) == NULL) return; devclass_get_devices (my_devclass, &devices, &devcount); if (devcount == 0) { /* We should find all devices by our self. We could alter other * devices, but we don't have a choise */ for (i = 0; (iobase = porttab [i]) != 0; i++) { if (!cx_is_free_res (dev, 1, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) continue; if (cx_probe_board (iobase, -1, -1) == 0) continue; devcount++; child = BUS_ADD_CHILD (dev, ISA_ORDER_SPECULATIVE, "cx", -1); if (child == NULL) return; device_set_desc_copy (child, "Cronyx Sigma"); device_set_driver (child, driver); bus_set_resource (child, SYS_RES_IOPORT, 0, iobase, NPORT); if (devcount >= NCX) break; } } else { static short porttab [] = { 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0x300, 0x320, 0x340, 0x360, 0x380, 0x3a0, 0x3c0, 0x3e0, 0 }; /* Lets check user choise. */ for (k = 0; k < devcount; k++) { if (bus_get_resource (devices[k], SYS_RES_IOPORT, 0, &iobase, &rescount) != 0) continue; for (i = 0; porttab [i] != 0; i++) { if (porttab [i] != iobase) continue; if (!cx_is_free_res (devices[k], 1, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) continue; if (cx_probe_board (iobase, -1, -1) == 0) continue; porttab [i] = -1; device_set_desc_copy (devices[k], "Cronyx Sigma"); break; } if (porttab [i] == 0) { device_delete_child ( device_get_parent (devices[k]), devices [k]); devices[k] = 0; continue; } } for (k = 0; k < devcount; k++) { if (devices[k] == 0) continue; if (bus_get_resource (devices[k], SYS_RES_IOPORT, 0, &iobase, &rescount) == 0) continue; for (i = 0; (iobase = porttab [i]) != 0; i++) { if (porttab [i] == -1) { continue; } if (!cx_is_free_res (devices[k], 1, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) continue; if (cx_probe_board (iobase, -1, -1) == 0) continue; bus_set_resource (devices[k], SYS_RES_IOPORT, 0, iobase, NPORT); porttab [i] = -1; device_set_desc_copy (devices[k], "Cronyx Sigma"); break; } if (porttab [i] == 0) { device_delete_child ( device_get_parent (devices[k]), devices [k]); } } free (devices, M_TEMP); } return; } static int cx_probe (device_t dev) { int unit = device_get_unit (dev); int i; u_long iobase, rescount; if (!device_get_desc (dev) || strcmp (device_get_desc (dev), "Cronyx Sigma")) return ENXIO; if (bus_get_resource (dev, SYS_RES_IOPORT, 0, &iobase, &rescount) != 0) { printf ("cx%d: Couldn't get IOPORT\n", unit); return ENXIO; } if (!cx_is_free_res (dev, 1, SYS_RES_IOPORT, iobase, iobase + NPORT, NPORT)) { printf ("cx%d: Resource IOPORT isn't free %lx\n", unit, iobase); return ENXIO; } for (i = 0; porttab [i] != 0; i++) { if (porttab [i] == iobase) { porttab [i] = -1; break; } } if (porttab [i] == 0) { return ENXIO; } if (!cx_probe_board (iobase, -1, -1)) { printf ("cx%d: probing for Sigma at %lx faild\n", unit, iobase); return ENXIO; } return 0; } #else /* __FreeBSD_version < 400000 */ static int cx_probe (struct isa_device *id) { cx_board_t *b; int i; #ifndef NETGRAPH if (! sppp_attach) { printf ("cx%d: no synchronous PPP driver configured\n", id->id_unit); return 0; } #endif if (id->id_iobase < 0) { /* Autodetect the adapter. */ for (i=0; ; i++) { if (! porttab[i]) { id->id_iobase = -1; return 0; } id->id_iobase = porttab[i]; if (id->id_unit > 0 && adapter[0] && adapter[0]->port == id->id_iobase) continue; if (id->id_unit > 1 && adapter[1] && adapter[1]->port == id->id_iobase) continue; if (! haveseen_isadev (id, CC_IOADDR | CC_QUIET) && cx_probe_board (id->id_iobase, -1, -1)) break; } } else if (! cx_probe_board (id->id_iobase, -1, -1)) return 0; if (id->id_drq < 0) { /* Find available 16-bit DRQ. */ for (i=0; ; ++i) { if (! dmatab[i]) { printf ("cx%d: no available drq found\n", id->id_unit); id->id_drq = -1; return 0; } id->id_drq = dmatab[i]; if (! haveseen_isadev (id, CC_DRQ | CC_QUIET) && !isa_dma_acquire (id->id_drq)) break; } } b = malloc (sizeof (cx_board_t), M_DEVBUF, M_WAITOK); if (!b) { printf ("cx:%d: Couldn't allocate memory\n", id->id_unit); return (ENXIO); } adapter[id->id_unit] = b; bzero (b, sizeof(cx_board_t)); if (! cx_open_board (b, id->id_unit, id->id_iobase, id->id_irq ? ffs (id->id_irq) - 1 : -1, id->id_drq)) { printf ("cx%d: cannot initialize adapter\n", id->id_unit); isa_dma_release (id->id_drq); adapter[id->id_unit] = 0; free (b, M_DEVBUF); return 0; } if (id->id_irq) { if (! probe_irq (b, ffs (id->id_irq) - 1)) printf ("cx%d: irq %d not functional\n", id->id_unit, ffs (id->id_irq) - 1); } else { /* Find available IRQ. */ for (i=0; ; ++i) { if (! irqtab[i]) { printf ("cx%d: no available irq found\n", id->id_unit); id->id_irq = -1; isa_dma_release (id->id_drq); adapter[id->id_unit] = 0; free (b, M_DEVBUF); return 0; } id->id_irq = 1 << irqtab[i]; if (haveseen_isadev (id, CC_IRQ | CC_QUIET)) continue; #ifdef KLD_MODULE if (register_intr (irqtab[i], 0, 0, (inthand2_t*) cx_intr, &net_imask, id->id_unit) != 0) continue; unregister_intr (irqtab[i], (inthand2_t*) cx_intr); #endif if (probe_irq (b, irqtab[i])) break; } } cx_init (b, b->num, b->port, ffs (id->id_irq) - 1, b->dma); cx_setup_board (b, 0, 0, 0); return 1; } #endif /* __FreeBSD_version < 400000 */ /* * The adapter is present, initialize the driver structures. */ #if __FreeBSD_version < 400000 static int cx_attach (struct isa_device *id) { #else static int cx_attach (device_t dev) { bdrv_t *bd = device_get_softc (dev); u_long iobase, drq, irq, rescount; int unit = device_get_unit (dev); int i; int s; #endif cx_board_t *b; cx_chan_t *c; drv_t *d; #if __FreeBSD_version >= 400000 KASSERT ((bd != NULL), ("cx%d: NULL device softc\n", unit)); bus_get_resource (dev, SYS_RES_IOPORT, 0, &iobase, &rescount); bd->base_rid = 0; bd->base_res = bus_alloc_resource (dev, SYS_RES_IOPORT, &bd->base_rid, iobase, iobase + NPORT, NPORT, RF_ACTIVE); if (! bd->base_res) { printf ("cx%d: cannot allocate base address\n", unit); return ENXIO; } if (bus_get_resource (dev, SYS_RES_DRQ, 0, &drq, &rescount) != 0) { for (i = 0; (drq = dmatab [i]) != 0; i++) { if (!cx_is_free_res (dev, 1, SYS_RES_DRQ, drq, drq + 1, 1)) continue; bus_set_resource (dev, SYS_RES_DRQ, 0, drq, 1); break; } if (dmatab[i] == 0) { bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); printf ("cx%d: Couldn't get DRQ\n", unit); return ENXIO; } } bd->drq_rid = 0; bd->drq_res = bus_alloc_resource (dev, SYS_RES_DRQ, &bd->drq_rid, drq, drq + 1, 1, RF_ACTIVE); if (! bd->drq_res) { printf ("cx%d: cannot allocate drq\n", unit); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } if (bus_get_resource (dev, SYS_RES_IRQ, 0, &irq, &rescount) != 0) { for (i = 0; (irq = irqtab [i]) != 0; i++) { if (!cx_is_free_res (dev, 1, SYS_RES_IRQ, irq, irq + 1, 1)) continue; bus_set_resource (dev, SYS_RES_IRQ, 0, irq, 1); break; } if (irqtab[i] == 0) { bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); printf ("cx%d: Couldn't get IRQ\n", unit); return ENXIO; } } bd->irq_rid = 0; bd->irq_res = bus_alloc_resource (dev, SYS_RES_IRQ, &bd->irq_rid, irq, irq + 1, 1, RF_ACTIVE); if (! bd->irq_res) { printf ("cx%d: Couldn't allocate irq\n", unit); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } b = malloc (sizeof (cx_board_t), M_DEVBUF, M_WAITOK); if (!b) { printf ("cx:%d: Couldn't allocate memory\n", unit); return (ENXIO); } adapter[unit] = b; bzero (b, sizeof(cx_board_t)); if (! cx_open_board (b, unit, iobase, irq, drq)) { printf ("cx%d: error loading firmware\n", unit); free (b, M_DEVBUF); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } bd->board = b; if (! probe_irq (b, irq)) { printf ("cx%d: irq %ld not functional\n", unit, irq); bd->board = 0; adapter [unit] = 0; free (b, M_DEVBUF); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); return ENXIO; } s = splhigh (); if (bus_setup_intr (dev, bd->irq_res, INTR_TYPE_NET, cx_intr, bd, &bd->intrhand)) { printf ("cx%d: Can't setup irq %ld\n", unit, irq); bd->board = 0; adapter [unit] = 0; free (b, M_DEVBUF); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); splx (s); return ENXIO; } cx_init (b, b->num, b->port, irq, drq); cx_setup_board (b, 0, 0, 0); #else /* __FreeBSD_version >= 400000 */ b = adapter[id->id_unit]; #endif /* __FreeBSD_version >= 400000 */ printf ("cx%d: \n", b->num, b->name); #if __FreeBSD_version < 400000 id->id_ointr = cx_intr; #endif for (c=b->chan; cchan+NCHAN; ++c) { #if __FreeBSD_version >= 400000 char *dnmt="tty %x"; char *dnmc="cua %x"; #endif if (c->type == T_NONE) continue; d = contigmalloc (sizeof(drv_t), M_DEVBUF, M_WAITOK, 0x100000, 0x1000000, 16, 0); channel [b->num*NCHAN + c->num] = d; bzero (d, sizeof(drv_t)); sprintf (d->name, "cx%d.%d", b->num, c->num); d->board = b; d->chan = c; d->tty.t_oproc = cx_oproc; d->tty.t_param = cx_param; #if __FreeBSD_version >= 400000 d->tty.t_stop = cx_stop; #endif d->dtrwait = 3 * hz; /* Default DTR off timeout is 3 seconds. */ d->open_dev = 0; c->sys = d; switch (c->type) { case T_SYNC_RS232: case T_SYNC_V35: case T_SYNC_RS449: case T_UNIV: case T_UNIV_RS232: case T_UNIV_RS449: case T_UNIV_V35: #ifdef NETGRAPH if (ng_make_node_common (&typestruct, &d->node) != 0) { printf ("%s: cannot make common node\n", d->name); channel [b->num*NCHAN + c->num] = 0; c->sys = 0; #if __FreeBSD_version < 400000 free (d, M_DEVBUF); #else contigfree (d, sizeof (*d), M_DEVBUF); #endif continue; } #if __FreeBSD_version >= 500000 NG_NODE_SET_PRIVATE (d->node, d); #else d->node->private = d; #endif sprintf (d->nodename, "%s%d", NG_CX_NODE_TYPE, c->board->num*NCHAN + c->num); if (ng_name_node (d->node, d->nodename)) { printf ("%s: cannot name node\n", d->nodename); #if __FreeBSD_version >= 500000 NG_NODE_UNREF (d->node); #else ng_rmnode (d->node); ng_unref (d->node); #endif channel [b->num*NCHAN + c->num] = 0; c->sys = 0; #if __FreeBSD_version < 400000 free (d, M_DEVBUF); #else contigfree (d, sizeof (*d), M_DEVBUF); #endif continue; } d->lo_queue.ifq_maxlen = IFQ_MAXLEN; d->hi_queue.ifq_maxlen = IFQ_MAXLEN; #if __FreeBSD_version >= 500000 mtx_init (&d->lo_queue.ifq_mtx, "cx_queue_lo", NULL, MTX_DEF); mtx_init (&d->hi_queue.ifq_mtx, "cx_queue_hi", NULL, MTX_DEF); #endif #else /*NETGRAPH*/ d->pp.pp_if.if_softc = d; #if __FreeBSD_version > 501000 if_initname (&d->pp.pp_if, "cx", b->num * NCHAN + c->num); #else d->pp.pp_if.if_unit = b->num * NCHAN + c->num; d->pp.pp_if.if_name = "cx"; #endif d->pp.pp_if.if_mtu = PP_MTU; d->pp.pp_if.if_flags = IFF_POINTOPOINT | IFF_MULTICAST; d->pp.pp_if.if_ioctl = cx_sioctl; d->pp.pp_if.if_start = cx_ifstart; d->pp.pp_if.if_watchdog = cx_ifwatchdog; d->pp.pp_if.if_init = cx_initialize; sppp_attach (&d->pp.pp_if); if_attach (&d->pp.pp_if); d->pp.pp_tlf = cx_tlf; d->pp.pp_tls = cx_tls; #if __FreeBSD_version >= 400000 || NBPFILTER > 0 /* If BPF is in the kernel, call the attach for it. * Size of PPP header is 4 bytes. */ bpfattach (&d->pp.pp_if, DLT_PPP, 4); #endif #endif /*NETGRAPH*/ } cx_start_chan (c, &d->buf, vtophys (&d->buf)); cx_register_receive (c, &cx_receive); cx_register_transmit (c, &cx_transmit); cx_register_error (c, &cx_error); cx_register_modem (c, &cx_modem); #if __FreeBSD_version >= 400000 dnmt[3] = 'x'+b->num; dnmc[3] = 'x'+b->num; d->devt[0] = make_dev (&cx_cdevsw, b->num*NCHAN + c->num, UID_ROOT, GID_WHEEL, 0644, dnmt, b->num*NCHAN + c->num); d->devt[1] = make_dev (&cx_cdevsw, b->num*NCHAN + c->num + 64, UID_ROOT, GID_WHEEL, 0600, "cx%d", b->num*NCHAN + c->num); d->devt[2] = make_dev (&cx_cdevsw, b->num*NCHAN + c->num + 128, UID_ROOT, GID_WHEEL, 0660, dnmc, b->num*NCHAN + c->num); } splx (s); return 0; #else /* __FreeBSD_version < 400000 */ } return 1; #endif } #if __FreeBSD_version >= 400000 static int cx_detach (device_t dev) { bdrv_t *bd = device_get_softc (dev); cx_board_t *b = bd->board; cx_chan_t *c; int s = splhigh (); /* Check if the device is busy (open). */ for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (!d || d->chan->type == T_NONE) continue; if (d->lock) { splx (s); return EBUSY; } if (c->mode == M_ASYNC && (d->tty.t_state & TS_ISOPEN) && (d->open_dev|0x2)) { splx (s); return EBUSY; } if (d->running) { splx (s); return EBUSY; } } /* Deactivate the timeout routine. And soft interrupt*/ if (led_timo[b->num].callout) untimeout (cx_led_off, b, led_timo[b->num]); for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = c->sys; if (!d || d->chan->type == T_NONE) continue; if (d->dtr_timeout_handle.callout) untimeout (cx_dtrwakeup, d, d->dtr_timeout_handle); if (d->dcd_timeout_handle.callout) untimeout (cx_carrier, c, d->dcd_timeout_handle); } bus_teardown_intr (dev, bd->irq_res, bd->intrhand); bus_deactivate_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_IRQ, bd->irq_rid, bd->irq_res); bus_deactivate_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_release_resource (dev, SYS_RES_DRQ, bd->drq_rid, bd->drq_res); bus_deactivate_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->irq_res); bus_release_resource (dev, SYS_RES_IOPORT, bd->base_rid, bd->base_res); cx_close_board (b); /* Detach the interfaces, free buffer memory. */ for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (!d || d->chan->type == T_NONE) continue; #ifdef NETGRAPH #if __FreeBSD_version >= 500000 if (d->node) { ng_rmnode_self (d->node); NG_NODE_UNREF (d->node); d->node = NULL; } mtx_destroy (&d->lo_queue.ifq_mtx); mtx_destroy (&d->hi_queue.ifq_mtx); #else ng_rmnode (d->node); d->node = NULL; #endif #else #if __FreeBSD_version >= 410000 && NBPFILTER > 0 /* Detach from the packet filter list of interfaces. */ bpfdetach (&d->pp.pp_if); #endif /* Detach from the sync PPP list. */ sppp_detach (&d->pp.pp_if); if_detach (&d->pp.pp_if); #endif destroy_dev (d->devt[0]); destroy_dev (d->devt[1]); destroy_dev (d->devt[2]); } cx_led_off (b); if (led_timo[b->num].callout) untimeout (cx_led_off, b, led_timo[b->num]); splx (s); s = splhigh (); for (c = b->chan; c < b->chan + NCHAN; ++c) { drv_t *d = (drv_t*) c->sys; if (!d || d->chan->type == T_NONE) continue; /* Deallocate buffers. */ contigfree (d, sizeof (*d), M_DEVBUF); } bd->board = 0; adapter [b->num] = 0; free (b, M_DEVBUF); splx (s); return 0; } #endif #ifndef NETGRAPH static void cx_ifstart (struct ifnet *ifp) { drv_t *d = ifp->if_softc; cx_start (d); } static void cx_ifwatchdog (struct ifnet *ifp) { drv_t *d = ifp->if_softc; cx_watchdog (d); } static void cx_tlf (struct sppp *sp) { drv_t *d = sp->pp_if.if_softc; CX_DEBUG (d, ("cx_tlf\n")); /* cx_set_dtr (d->chan, 0);*/ /* cx_set_rts (d->chan, 0);*/ sp->pp_down (sp); } static void cx_tls (struct sppp *sp) { drv_t *d = sp->pp_if.if_softc; CX_DEBUG (d, ("cx_tls\n")); sp->pp_up (sp); } /* * Initialization of interface. * It seems to be never called by upper level. */ static void cx_initialize (void *softc) { drv_t *d = softc; CX_DEBUG (d, ("cx_initialize\n")); } /* * Process an ioctl request. */ static int cx_sioctl (struct ifnet *ifp, u_long cmd, caddr_t data) { drv_t *d = ifp->if_softc; int error, s, was_up, should_be_up; /* No socket ioctls while the channel is in async mode. */ if (d->chan->type == T_NONE || d->chan->mode == M_ASYNC) return EBUSY; /* Socket ioctls on slave subchannels are not allowed. */ was_up = (ifp->if_flags & IFF_RUNNING) != 0; error = sppp_ioctl (ifp, cmd, data); if (error) return error; if (! (ifp->if_flags & IFF_DEBUG)) d->chan->debug = 0; else if (! d->chan->debug) d->chan->debug = 1; switch (cmd) { default: CX_DEBUG2 (d, ("ioctl 0x%lx\n", cmd)); return 0; case SIOCADDMULTI: CX_DEBUG2 (d, ("SIOCADDMULTI\n")); return 0; case SIOCDELMULTI: CX_DEBUG2 (d, ("SIOCDELMULTI\n")); return 0; case SIOCSIFFLAGS: CX_DEBUG2 (d, ("SIOCSIFFLAGS\n")); break; case SIOCSIFADDR: CX_DEBUG2 (d, ("SIOCSIFADDR\n")); break; } /* We get here only in case of SIFFLAGS or SIFADDR. */ s = splhigh (); should_be_up = (ifp->if_flags & IFF_RUNNING) != 0; if (!was_up && should_be_up) { /* Interface goes up -- start it. */ cx_up (d); cx_start (d); } else if (was_up && !should_be_up) { /* Interface is going down -- stop it. */ /* if ((d->pp.pp_flags & PP_FR) || (ifp->if_flags & PP_CISCO))*/ cx_down (d); } splx (s); return 0; } #endif /*NETGRAPH*/ /* * Stop the interface. Called on splimp(). */ static void cx_down (drv_t *d) { int s = splhigh (); CX_DEBUG (d, ("cx_down\n")); cx_set_dtr (d->chan, 0); cx_set_rts (d->chan, 0); d->running = 0; splx (s); } /* * Start the interface. Called on splimp(). */ static void cx_up (drv_t *d) { int s = splhigh (); CX_DEBUG (d, ("cx_up\n")); cx_set_dtr (d->chan, 1); cx_set_rts (d->chan, 1); d->running = 1; splx (s); } /* * Start output on the (slave) interface. Get another datagram to send * off of the interface queue, and copy it to the interface * before starting the output. */ static void cx_send (drv_t *d) { struct mbuf *m; u_short len; CX_DEBUG2 (d, ("cx_send\n")); /* No output if the interface is down. */ if (! d->running) return; /* No output if the modem is off. */ if (! cx_get_dsr (d->chan) && ! cx_get_loop(d->chan)) return; if (cx_buf_free (d->chan)) { /* Get the packet to send. */ #ifdef NETGRAPH IF_DEQUEUE (&d->hi_queue, m); if (! m) IF_DEQUEUE (&d->lo_queue, m); #else m = sppp_dequeue (&d->pp.pp_if); #endif if (! m) return; #if (__FreeBSD_version >= 400000 || NBPFILTER > 0) && !defined (NETGRAPH) if (d->pp.pp_if.if_bpf) #if __FreeBSD_version >= 500000 BPF_MTAP (&d->pp.pp_if, m); #else bpf_mtap (&d->pp.pp_if, m); #endif #endif len = m->m_pkthdr.len; if (! m->m_next) cx_send_packet (d->chan, (u_char*)mtod (m, caddr_t), len, 0); else { u_char buf [DMABUFSZ]; m_copydata (m, 0, len, buf); cx_send_packet (d->chan, buf, len, 0); } m_freem (m); /* Set up transmit timeout, 10 seconds. */ #ifdef NETGRAPH d->timeout = 10; #else d->pp.pp_if.if_timer = 10; #endif } #ifndef NETGRAPH d->pp.pp_if.if_flags |= IFF_OACTIVE; #endif } /* * Start output on the interface. * Always called on splimp(). */ static void cx_start (drv_t *d) { int s = splhigh (); if (d->running) { if (! d->chan->dtr) cx_set_dtr (d->chan, 1); if (! d->chan->rts) cx_set_rts (d->chan, 1); cx_send (d); } splx (s); } /* * Handle transmit timeouts. * Recover after lost transmit interrupts. * Always called on splimp(). */ static void cx_watchdog (drv_t *d) { int s = splhigh (); CX_DEBUG (d, ("device timeout\n")); if (d->running) { cx_setup_chan (d->chan); cx_start_chan (d->chan, 0, 0); cx_set_dtr (d->chan, 1); cx_set_rts (d->chan, 1); cx_start (d); } splx (s); } /* * Transmit callback function. */ static void cx_transmit (cx_chan_t *c, void *attachment, int len) { drv_t *d = c->sys; if (!d) return; if (c->mode == M_ASYNC) { d->tty.t_state &= ~(TS_BUSY | TS_FLUSH); d->atimeout = 0; if (d->tty.t_dev) { d->intr_action |= CX_WRITE; MY_SOFT_INTR = 1; #if __FreeBSD_version >= 500000 swi_sched (cx_fast_ih, 0); #else setsofttty (); #endif } return; } #ifdef NETGRAPH d->timeout = 0; #else ++d->pp.pp_if.if_opackets; d->pp.pp_if.if_flags &= ~IFF_OACTIVE; d->pp.pp_if.if_timer = 0; #endif cx_start (d); } /* * Process the received packet. */ static void cx_receive (cx_chan_t *c, char *data, int len) { drv_t *d = c->sys; struct mbuf *m; char *cc = data; #if __FreeBSD_version >= 500000 && defined NETGRAPH int error; #endif if (!d) return; if (c->mode == M_ASYNC) { if (d->tty.t_state & TS_ISOPEN) { async_q *q = &d->aqueue; int size = BF_SZ - 1 - AQ_GSZ (q); if (len <= 0 && !size) return; if (len > size) { c->ierrs++; cx_error (c, CX_OVERRUN); len = size - 1; } while (len--) { AQ_PUSH (q, *(unsigned char *)cc); cc++; } d->intr_action |= CX_READ; MY_SOFT_INTR = 1; #if __FreeBSD_version >= 500000 swi_sched (cx_fast_ih, 0); #else setsofttty (); #endif } return; } if (! d->running) return; m = makembuf (data, len); if (! m) { CX_DEBUG (d, ("no memory for packet\n")); #ifndef NETGRAPH ++d->pp.pp_if.if_iqdrops; #endif return; } if (c->debug > 1) printmbuf (m); #ifdef NETGRAPH m->m_pkthdr.rcvif = 0; #if __FreeBSD_version >= 500000 NG_SEND_DATA_ONLY (error, d->hook, m); #else ng_queue_data (d->hook, m, 0); #endif #else ++d->pp.pp_if.if_ipackets; m->m_pkthdr.rcvif = &d->pp.pp_if; #if __FreeBSD_version >= 400000 || NBPFILTER > 0 /* Check if there's a BPF listener on this interface. * If so, hand off the raw packet to bpf. */ if (d->pp.pp_if.if_bpf) #if __FreeBSD_version >= 500000 BPF_TAP (&d->pp.pp_if, data, len); #else bpf_tap (&d->pp.pp_if, data, len); #endif #endif sppp_input (&d->pp.pp_if, m); #endif } #define CONDITION(t,tp) (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON))\ && (!(tp->t_iflag & BRKINT) || (tp->t_iflag & IGNBRK))\ && (!(tp->t_iflag & PARMRK)\ || (tp->t_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK))\ && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN))\ && linesw[tp->t_line].l_rint == ttyinput) /* * Error callback function. */ static void cx_error (cx_chan_t *c, int data) { drv_t *d = c->sys; async_q *q; if (!d) return; q = &(d->aqueue); switch (data) { case CX_FRAME: CX_DEBUG (d, ("frame error\n")); if (c->mode == M_ASYNC && (d->tty.t_state & TS_ISOPEN) && (AQ_GSZ (q) < BF_SZ - 1) && (!CONDITION((&d->tty.t_termios), (&d->tty)) || !(d->tty.t_iflag & (IGNPAR | PARMRK)))) { AQ_PUSH (q, TTY_FE); d->intr_action |= CX_READ; MY_SOFT_INTR = 1; #if __FreeBSD_version >= 500000 swi_sched (cx_fast_ih, 0); #else setsofttty (); #endif } #ifndef NETGRAPH else ++d->pp.pp_if.if_ierrors; #endif break; case CX_CRC: CX_DEBUG (d, ("crc error\n")); if (c->mode == M_ASYNC && (d->tty.t_state & TS_ISOPEN) && (AQ_GSZ (q) < BF_SZ - 1) && (!CONDITION((&d->tty.t_termios), (&d->tty)) || !(d->tty.t_iflag & INPCK) || !(d->tty.t_iflag & (IGNPAR | PARMRK)))) { AQ_PUSH (q, TTY_PE); d->intr_action |= CX_READ; MY_SOFT_INTR = 1; #if __FreeBSD_version >= 500000 swi_sched (cx_fast_ih, 0); #else setsofttty (); #endif } #ifndef NETGRAPH else ++d->pp.pp_if.if_ierrors; #endif break; case CX_OVERRUN: CX_DEBUG (d, ("overrun error\n")); #ifdef TTY_OE if (c->mode == M_ASYNC && (d->tty.t_state & TS_ISOPEN) && (AQ_GSZ (q) < BF_SZ - 1) && (!CONDITION((&d->tty.t_termios), (&d->tty)))) { AQ_PUSH (q, TTY_OE); d->intr_action |= CX_READ; MY_SOFT_INTR = 1; #if __FreeBSD_version >= 500000 swi_sched (cx_fast_ih, 0); #else setsofttty (); #endif } #endif #ifndef NETGRAPH else { ++d->pp.pp_if.if_collisions; ++d->pp.pp_if.if_ierrors; } #endif break; case CX_OVERFLOW: CX_DEBUG (d, ("overflow error\n")); #ifndef NETGRAPH if (c->mode != M_ASYNC) ++d->pp.pp_if.if_ierrors; #endif break; case CX_UNDERRUN: CX_DEBUG (d, ("underrun error\n")); if (c->mode != M_ASYNC) { #ifdef NETGRAPH d->timeout = 0; #else ++d->pp.pp_if.if_oerrors; d->pp.pp_if.if_flags &= ~IFF_OACTIVE; d->pp.pp_if.if_timer = 0; cx_start (d); #endif } break; case CX_BREAK: CX_DEBUG (d, ("break error\n")); if (c->mode == M_ASYNC && (d->tty.t_state & TS_ISOPEN) && (AQ_GSZ (q) < BF_SZ - 1) && (!CONDITION((&d->tty.t_termios), (&d->tty)) || !(d->tty.t_iflag & (IGNBRK | BRKINT | PARMRK)))) { AQ_PUSH (q, TTY_BI); d->intr_action |= CX_READ; MY_SOFT_INTR = 1; #if __FreeBSD_version >= 500000 swi_sched (cx_fast_ih, 0); #else setsofttty (); #endif } #ifndef NETGRAPH else ++d->pp.pp_if.if_ierrors; #endif break; default: CX_DEBUG (d, ("error #%d\n", data)); } } #if __FreeBSD_version < 500000 static int cx_open (dev_t dev, int flag, int mode, struct proc *p) #else static int cx_open (dev_t dev, int flag, int mode, struct thread *td) #endif { int unit = UNIT (dev); drv_t *d; int error; if (unit >= NCX*NCHAN || ! (d = channel[unit])) return ENXIO; CX_DEBUG2 (d, ("cx_open unit=%d, flag=0x%x, mode=0x%x\n", unit, flag, mode)); if (d->chan->mode != M_ASYNC || IF_CUNIT(dev)) { d->open_dev |= 0x1; return 0; } #if __FreeBSD_version >= 400000 dev->si_tty = &d->tty; #endif d->tty.t_dev = dev; again: if (d->dtroff) { error = tsleep (&d->dtrwait, TTIPRI | PCATCH, "cxdtr", 0); if (error) return error; goto again; } if ((d->tty.t_state & TS_ISOPEN) && (d->tty.t_state & TS_XCLUDE) && #if __FreeBSD_version >= 500000 suser (td)) #else p->p_ucred->cr_uid != 0) #endif return EBUSY; if (d->tty.t_state & TS_ISOPEN) { /* * Cannot open /dev/cua if /dev/tty already opened. */ if (CALLOUT (dev) && ! d->callout) return EBUSY; /* * Opening /dev/tty when /dev/cua is already opened. * Wait for close, then try again. */ if (! CALLOUT (dev) && d->callout) { if (flag & O_NONBLOCK) return EBUSY; error = tsleep (d, TTIPRI | PCATCH, "cxbi", 0); if (error) return error; goto again; } } else if (d->lock && ! CALLOUT (dev) && (flag & O_NONBLOCK)) /* * We try to open /dev/tty in non-blocking mode * while somebody is already waiting for carrier on it. */ return EBUSY; else { ttychars (&d->tty); if (d->tty.t_ispeed == 0) { d->tty.t_iflag = 0; d->tty.t_oflag = 0; d->tty.t_lflag = 0; d->tty.t_cflag = CREAD | CS8 | HUPCL; d->tty.t_ispeed = d->chan->rxbaud; d->tty.t_ospeed = d->chan->txbaud; } if (CALLOUT (dev)) d->tty.t_cflag |= CLOCAL; else d->tty.t_cflag &= ~CLOCAL; cx_param (&d->tty, &d->tty.t_termios); ttsetwater (&d->tty); } splhigh (); if (! (d->tty.t_state & TS_ISOPEN)) { cx_start_chan (d->chan, 0, 0); cx_set_dtr (d->chan, 1); cx_set_rts (d->chan, 1); d->cd = cx_get_cd (d->chan); if (CALLOUT (dev) || cx_get_cd (d->chan)) ttyld_modem(&d->tty, 1); } if (! (flag & O_NONBLOCK) && ! (d->tty.t_cflag & CLOCAL) && ! (d->tty.t_state & TS_CARR_ON)) { /* Lock the channel against cxconfig while we are * waiting for carrier. */ d->lock++; error = tsleep (&d->tty.t_rawq, TTIPRI | PCATCH, "cxdcd", 0); /* Unlock the channel. */ d->lock--; spl0 (); if (error) goto failed; goto again; } error = ttyld_open (&d->tty, dev); - disc_optim (&d->tty, &d->tty.t_termios); + ttyldoptim (&d->tty); spl0 (); if (error) { failed: if (! (d->tty.t_state & TS_ISOPEN)) { splhigh (); cx_set_dtr (d->chan, 0); cx_set_rts (d->chan, 0); if (d->dtrwait) { d->dtr_timeout_handle = timeout (cx_dtrwakeup, d, d->dtrwait); d->dtroff = 1; } spl0 (); } return error; } if (d->tty.t_state & TS_ISOPEN) d->callout = CALLOUT (dev) ? 1 : 0; d->open_dev |= 0x2; CX_DEBUG2 (d, ("cx_open done\n")); return 0; } #if __FreeBSD_version < 500000 static int cx_close (dev_t dev, int flag, int mode, struct proc *p) #else static int cx_close (dev_t dev, int flag, int mode, struct thread *td) #endif { drv_t *d = channel [UNIT (dev)]; int s; CX_DEBUG2 (d, ("cx_close\n")); if ((!(d->open_dev&0x2)) || IF_CUNIT(dev)){ d->open_dev &= ~0x1; return 0; } s = splhigh (); ttyld_close(&d->tty, flag); - disc_optim (&d->tty, &d->tty.t_termios); + ttyldoptim (&d->tty); /* Disable receiver. * Transmitter continues sending the queued data. */ cx_enable_receive (d->chan, 0); /* Clear DTR and RTS. */ if ((d->tty.t_cflag & HUPCL) || ! (d->tty.t_state & TS_ISOPEN)) { cx_set_dtr (d->chan, 0); cx_set_rts (d->chan, 0); if (d->dtrwait) { d->dtr_timeout_handle = timeout (cx_dtrwakeup, d, d->dtrwait); d->dtroff = 1; } } ttyclose (&d->tty); splx (s); d->callout = 0; /* Wake up bidirectional opens. */ wakeup (d); d->open_dev &= ~0x2; return 0; } static int cx_read (dev_t dev, struct uio *uio, int flag) { drv_t *d = channel [UNIT (dev)]; if (d) CX_DEBUG2 (d, ("cx_read\n")); if (!d || d->chan->mode != M_ASYNC || IF_CUNIT(dev)) return EBADF; return ttyld_read (&d->tty, uio, flag); } static int cx_write (dev_t dev, struct uio *uio, int flag) { drv_t *d = channel [UNIT (dev)]; if (d) CX_DEBUG2 (d, ("cx_write\n")); if (!d || d->chan->mode != M_ASYNC || IF_CUNIT(dev)) return EBADF; return ttyld_write (&d->tty, uio, flag); } static int cx_modem_status (drv_t *d) { int status = 0, s = splhigh (); /* Already opened by someone or network interface is up? */ if ((d->chan->mode == M_ASYNC && (d->tty.t_state & TS_ISOPEN) && (d->open_dev|0x2)) || (d->chan->mode != M_ASYNC && d->running)) status = TIOCM_LE; /* always enabled while open */ if (cx_get_dsr (d->chan)) status |= TIOCM_DSR; if (cx_get_cd (d->chan)) status |= TIOCM_CD; if (cx_get_cts (d->chan)) status |= TIOCM_CTS; if (d->chan->dtr) status |= TIOCM_DTR; if (d->chan->rts) status |= TIOCM_RTS; splx (s); return status; } #if __FreeBSD_version < 500000 static int cx_ioctl (dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) #else static int cx_ioctl (dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td) #endif { drv_t *d = channel [UNIT (dev)]; cx_chan_t *c; struct serial_statistics *st; int error, s; char mask[16]; if (!d || !(c = d->chan)) return EINVAL; switch (cmd) { case SERIAL_GETREGISTERED: CX_DEBUG2 (d, ("ioctl: getregistered\n")); bzero (mask, sizeof(mask)); for (s=0; sp_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; s = splhigh (); cx_set_port (c, *(int *)data); splx (s); return 0; #ifndef NETGRAPH case SERIAL_GETPROTO: CX_DEBUG2 (d, ("ioctl: getproto\n")); s = splhigh (); strcpy ((char*)data, (c->mode == M_ASYNC) ? "async" : (d->pp.pp_flags & PP_FR) ? "fr" : (d->pp.pp_if.if_flags & PP_CISCO) ? "cisco" : "ppp"); splx (s); return 0; case SERIAL_SETPROTO: CX_DEBUG2 (d, ("ioctl: setproto\n")); /* Only for superuser! */ #if __FreeBSD_version < 400000 error = suser (p->p_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; if (c->mode == M_ASYNC) return EBUSY; if (d->pp.pp_if.if_flags & IFF_RUNNING) return EBUSY; if (! strcmp ("cisco", (char*)data)) { d->pp.pp_flags &= ~(PP_FR); d->pp.pp_flags |= PP_KEEPALIVE; d->pp.pp_if.if_flags |= PP_CISCO; } else if (! strcmp ("fr", (char*)data)) { d->pp.pp_if.if_flags &= ~(PP_CISCO); d->pp.pp_flags |= PP_FR | PP_KEEPALIVE; } else if (! strcmp ("ppp", (char*)data)) { d->pp.pp_flags &= ~(PP_FR | PP_KEEPALIVE); d->pp.pp_if.if_flags &= ~(PP_CISCO); } else return EINVAL; return 0; case SERIAL_GETKEEPALIVE: CX_DEBUG2 (d, ("ioctl: getkeepalive\n")); if ((d->pp.pp_flags & PP_FR) || (d->pp.pp_if.if_flags & PP_CISCO) || (c->mode == M_ASYNC)) return EINVAL; s = splhigh (); *(int*)data = (d->pp.pp_flags & PP_KEEPALIVE) ? 1 : 0; splx (s); return 0; case SERIAL_SETKEEPALIVE: CX_DEBUG2 (d, ("ioctl: setkeepalive\n")); /* Only for superuser! */ #if __FreeBSD_version < 400000 error = suser (p->p_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; if ((d->pp.pp_flags & PP_FR) || (d->pp.pp_if.if_flags & PP_CISCO)) return EINVAL; s = splhigh (); if (*(int*)data) d->pp.pp_flags |= PP_KEEPALIVE; else d->pp.pp_flags &= ~PP_KEEPALIVE; splx (s); return 0; #endif /*NETGRAPH*/ case SERIAL_GETMODE: CX_DEBUG2 (d, ("ioctl: getmode\n")); s = splhigh (); *(int*)data = (c->mode == M_ASYNC) ? SERIAL_ASYNC : SERIAL_HDLC; splx (s); return 0; case SERIAL_SETMODE: CX_DEBUG2 (d, ("ioctl: setmode\n")); /* Only for superuser! */ #if __FreeBSD_version < 400000 error = suser (p->p_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; /* Somebody is waiting for carrier? */ if (d->lock) return EBUSY; /* /dev/ttyXX is already opened by someone? */ if (c->mode == M_ASYNC && (d->tty.t_state & TS_ISOPEN) && (d->open_dev|0x2)) return EBUSY; /* Network interface is up? * Cannot change to async mode. */ if (c->mode != M_ASYNC && d->running && (*(int*)data == SERIAL_ASYNC)) return EBUSY; s = splhigh (); if (c->mode == M_HDLC && *(int*)data == SERIAL_ASYNC) { cx_set_mode (c, M_ASYNC); cx_enable_receive (c, 0); cx_enable_transmit (c, 0); } else if (c->mode == M_ASYNC && *(int*)data == SERIAL_HDLC) { cx_set_mode (c, M_HDLC); cx_enable_receive (c, 1); cx_enable_transmit (c, 1); } splx (s); return 0; case SERIAL_GETSTAT: CX_DEBUG2 (d, ("ioctl: getestat\n")); st = (struct serial_statistics*) data; s = splhigh (); st->rintr = c->rintr; st->tintr = c->tintr; st->mintr = c->mintr; st->ibytes = c->ibytes; st->ipkts = c->ipkts; st->ierrs = c->ierrs; st->obytes = c->obytes; st->opkts = c->opkts; st->oerrs = c->oerrs; splx (s); return 0; case SERIAL_CLRSTAT: CX_DEBUG2 (d, ("ioctl: clrstat\n")); /* Only for superuser! */ #if __FreeBSD_version < 400000 error = suser (p->p_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; s = splhigh (); c->rintr = 0; c->tintr = 0; c->mintr = 0; c->ibytes = 0; c->ipkts = 0; c->ierrs = 0; c->obytes = 0; c->opkts = 0; c->oerrs = 0; splx (s); return 0; case SERIAL_GETBAUD: CX_DEBUG2 (d, ("ioctl: getbaud\n")); if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); *(long*)data = cx_get_baud(c); splx (s); return 0; case SERIAL_SETBAUD: CX_DEBUG2 (d, ("ioctl: setbaud\n")); /* Only for superuser! */ #if __FreeBSD_version < 400000 error = suser (p->p_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); cx_set_baud (c, *(long*)data); splx (s); return 0; case SERIAL_GETLOOP: CX_DEBUG2 (d, ("ioctl: getloop\n")); if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); *(int*)data = cx_get_loop (c); splx (s); return 0; case SERIAL_SETLOOP: CX_DEBUG2 (d, ("ioctl: setloop\n")); /* Only for superuser! */ #if __FreeBSD_version < 400000 error = suser (p->p_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); cx_set_loop (c, *(int*)data); splx (s); return 0; case SERIAL_GETDPLL: CX_DEBUG2 (d, ("ioctl: getdpll\n")); if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); *(int*)data = cx_get_dpll (c); splx (s); return 0; case SERIAL_SETDPLL: CX_DEBUG2 (d, ("ioctl: setdpll\n")); /* Only for superuser! */ #if __FreeBSD_version < 400000 error = suser (p->p_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); cx_set_dpll (c, *(int*)data); splx (s); return 0; case SERIAL_GETNRZI: CX_DEBUG2 (d, ("ioctl: getnrzi\n")); if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); *(int*)data = cx_get_nrzi (c); splx (s); return 0; case SERIAL_SETNRZI: CX_DEBUG2 (d, ("ioctl: setnrzi\n")); /* Only for superuser! */ #if __FreeBSD_version < 400000 error = suser (p->p_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; if (c->mode == M_ASYNC) return EINVAL; s = splhigh (); cx_set_nrzi (c, *(int*)data); splx (s); return 0; case SERIAL_GETDEBUG: CX_DEBUG2 (d, ("ioctl: getdebug\n")); s = splhigh (); *(int*)data = c->debug; splx (s); return 0; case SERIAL_SETDEBUG: CX_DEBUG2 (d, ("ioctl: setdebug\n")); /* Only for superuser! */ #if __FreeBSD_version < 400000 error = suser (p->p_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; s = splhigh (); c->debug = *(int*)data; splx (s); #ifndef NETGRAPH if (d->chan->debug) d->pp.pp_if.if_flags |= IFF_DEBUG; else d->pp.pp_if.if_flags &= (~IFF_DEBUG); #endif return 0; } if (c->mode == M_ASYNC) { #if __FreeBSD_version >= 502113 error = ttyioctl (dev, cmd, data, flag, td); - disc_optim (&d->tty, &d->tty.t_termios); + ttyldoptim (&d->tty); if (error != ENOTTY) { if (error) CX_DEBUG2 (d, ("ttioctl: 0x%lx, error %d\n", cmd, error)); return error; } #else #if __FreeBSD_version >= 500000 error = (*linesw[d->tty.t_line].l_ioctl) (&d->tty, cmd, data, flag, td); #else error = (*linesw[d->tty.t_line].l_ioctl) (&d->tty, cmd, data, flag, p); #endif - disc_optim (&d->tty, &d->tty.t_termios); + ttyldoptim (&d->tty); if (error != ENOIOCTL) { if (error) CX_DEBUG2 (d, ("l_ioctl: 0x%lx, error %d\n", cmd, error)); return error; } error = ttioctl (&d->tty, cmd, data, flag); - disc_optim (&d->tty, &d->tty.t_termios); + ttyldoptim (&d->tty); if (error != ENOIOCTL) { if (error) CX_DEBUG2 (d, ("ttioctl: 0x%lx, error %d\n", cmd, error)); return error; } #endif } switch (cmd) { case TIOCSBRK: /* Start sending line break */ CX_DEBUG2 (d, ("ioctl: tiocsbrk\n")); s = splhigh (); cx_send_break (c, 500); splx (s); return 0; case TIOCCBRK: /* Stop sending line break */ CX_DEBUG2 (d, ("ioctl: tioccbrk\n")); return 0; case TIOCSDTR: /* Set DTR */ CX_DEBUG2 (d, ("ioctl: tiocsdtr\n")); s = splhigh (); cx_set_dtr (c, 1); splx (s); return 0; case TIOCCDTR: /* Clear DTR */ CX_DEBUG2 (d, ("ioctl: tioccdtr\n")); s = splhigh (); cx_set_dtr (c, 0); splx (s); return 0; case TIOCMSET: /* Set DTR/RTS */ CX_DEBUG2 (d, ("ioctl: tiocmset\n")); s = splhigh (); cx_set_dtr (c, (*(int*)data & TIOCM_DTR) ? 1 : 0); cx_set_rts (c, (*(int*)data & TIOCM_RTS) ? 1 : 0); splx (s); return 0; case TIOCMBIS: /* Add DTR/RTS */ CX_DEBUG2 (d, ("ioctl: tiocmbis\n")); s = splhigh (); if (*(int*)data & TIOCM_DTR) cx_set_dtr (c, 1); if (*(int*)data & TIOCM_RTS) cx_set_rts (c, 1); splx (s); return 0; case TIOCMBIC: /* Clear DTR/RTS */ CX_DEBUG2 (d, ("ioctl: tiocmbic\n")); s = splhigh (); if (*(int*)data & TIOCM_DTR) cx_set_dtr (c, 0); if (*(int*)data & TIOCM_RTS) cx_set_rts (c, 0); splx (s); return 0; case TIOCMGET: /* Get modem status */ CX_DEBUG2 (d, ("ioctl: tiocmget\n")); *(int*)data = cx_modem_status (d); return 0; #ifdef TIOCMSDTRWAIT case TIOCMSDTRWAIT: CX_DEBUG2 (d, ("ioctl: tiocmsdtrwait\n")); /* Only for superuser! */ #if __FreeBSD_version < 400000 error = suser (p->p_ucred, &p->p_acflag); #elif __FreeBSD_version < 500000 error = suser (p); #else /* __FreeBSD_version >= 500000 */ error = suser (td); #endif /* __FreeBSD_version >= 500000 */ if (error) return error; s = splhigh (); d->dtrwait = *(int*)data * hz / 100; splx (s); return 0; #endif #ifdef TIOCMGDTRWAIT case TIOCMGDTRWAIT: CX_DEBUG2 (d, ("ioctl: tiocmgdtrwait\n")); s = splhigh (); *(int*)data = d->dtrwait * 100 / hz; splx (s); return 0; #endif } CX_DEBUG2 (d, ("ioctl: 0x%lx\n", cmd)); return ENOTTY; } /* * Wake up opens() waiting for DTR ready. */ static void cx_dtrwakeup (void *arg) { drv_t *d = arg; d->dtroff = 0; wakeup (&d->dtrwait); } +#if __FreeBSD_version < 502113 static void -disc_optim(tp, t) +ttyldoptim(tp) struct tty *tp; - struct termios *t; { + struct termios *t; + + t = &tp->t_termios; if (CONDITION(t,tp)) tp->t_state |= TS_CAN_BYPASS_L_RINT; else tp->t_state &= ~TS_CAN_BYPASS_L_RINT; } +#endif #if __FreeBSD_version >= 500000 void cx_softintr (void *unused) #else void cx_softintr () #endif { drv_t *d; async_q *q; int i, s, ic, k; while (MY_SOFT_INTR) { MY_SOFT_INTR = 0; for (i=0; ichan || d->chan->type == T_NONE || d->chan->mode != M_ASYNC || !d->tty.t_dev) continue; s = splhigh (); if (d->intr_action & CX_READ) { q = &(d->aqueue); if (d->tty.t_state & TS_CAN_BYPASS_L_RINT) { k = AQ_GSZ(q); if (d->tty.t_rawq.c_cc + k > d->tty.t_ihiwat && (d->tty.t_cflag & CRTS_IFLOW || d->tty.t_iflag & IXOFF) && !(d->tty.t_state & TS_TBLOCK)) ttyblock(&d->tty); d->tty.t_rawcc += k; while (k>0) { k--; AQ_POP (q, ic); splx (s); putc (ic, &d->tty.t_rawq); s = splhigh (); } ttwakeup(&d->tty); if (d->tty.t_state & TS_TTSTOP && (d->tty.t_iflag & IXANY || d->tty.t_cc[VSTART] == d->tty.t_cc[VSTOP])) { d->tty.t_state &= ~TS_TTSTOP; d->tty.t_lflag &= ~FLUSHO; d->intr_action |= CX_WRITE; } } else { while (q->end != q->beg) { AQ_POP (q, ic); splx (s); ttyld_rint (&d->tty, ic); s = splhigh (); } } d->intr_action &= ~CX_READ; } splx (s); s = splhigh (); if (d->intr_action & CX_WRITE) { if (d->tty.t_line) ttyld_start (&d->tty); else cx_oproc (&d->tty); d->intr_action &= ~CX_WRITE; } splx (s); } } } /* * Fill transmitter buffer with data. */ static void cx_oproc (struct tty *tp) { int s = splhigh (), k; drv_t *d = channel [UNIT (tp->t_dev)]; static u_char buf[DMABUFSZ]; u_char *p; u_short len = 0, sublen = 0; if (!d) { splx (s); return; } CX_DEBUG2 (d, ("cx_oproc\n")); if (tp->t_cflag & CRTSCTS && (tp->t_state & TS_TBLOCK) && d->chan->rts) cx_set_rts (d->chan, 0); else if (tp->t_cflag & CRTSCTS && ! (tp->t_state & TS_TBLOCK) && ! d->chan->rts) cx_set_rts (d->chan, 1); if (! (tp->t_state & (TS_TIMEOUT | TS_TTSTOP))) { /* Start transmitter. */ cx_enable_transmit (d->chan, 1); /* Is it busy? */ if (! cx_buf_free (d->chan)) { tp->t_state |= TS_BUSY; splx (s); return; } if (tp->t_iflag & IXOFF) { p = (buf + (DMABUFSZ/2)); sublen = q_to_b (&tp->t_outq, p, (DMABUFSZ/2)); k = sublen; while (k--) { /* Send XON/XOFF out of band. */ if (*p == tp->t_cc[VSTOP]) { cx_xflow_ctl (d->chan, 0); p++; continue; } if (*p == tp->t_cc[VSTART]) { cx_xflow_ctl (d->chan, 1); p++; continue; } buf[len] = *p; len++; p++; } } else { p = buf; len = q_to_b (&tp->t_outq, p, (DMABUFSZ/2)); } if (len) { cx_send_packet (d->chan, buf, len, 0); tp->t_state |= TS_BUSY; d->atimeout = 10; CX_DEBUG2 (d, ("out %d bytes\n", len)); } } ttwwakeup (tp); splx (s); } static int cx_param (struct tty *tp, struct termios *t) { drv_t *d = channel [UNIT (tp->t_dev)]; int s, bits, parity; if (!d) return EINVAL; s = splhigh (); if (t->c_ospeed == 0) { /* Clear DTR and RTS. */ cx_set_dtr (d->chan, 0); splx (s); CX_DEBUG2 (d, ("cx_param (hangup)\n")); return 0; } CX_DEBUG2 (d, ("cx_param\n")); /* Check requested parameters. */ if (t->c_ospeed < 300 || t->c_ospeed > 256*1024) { splx (s); return EINVAL; } if (t->c_ispeed && (t->c_ispeed < 300 || t->c_ispeed > 256*1024)) { splx (s); return EINVAL; } /* And copy them to tty and channel structures. */ tp->t_ispeed = t->c_ispeed = tp->t_ospeed = t->c_ospeed; tp->t_cflag = t->c_cflag; /* Set character length and parity mode. */ switch (t->c_cflag & CSIZE) { default: case CS8: bits = 8; break; case CS7: bits = 7; break; case CS6: bits = 6; break; case CS5: bits = 5; break; } parity = ((t->c_cflag & PARENB) ? 1 : 0) * (1 + ((t->c_cflag & PARODD) ? 0 : 1)); /* Set current channel number. */ if (! d->chan->dtr) cx_set_dtr (d->chan, 1); - disc_optim (&d->tty, &d->tty.t_termios); + ttyldoptim (&d->tty); cx_set_async_param (d->chan, t->c_ospeed, bits, parity, (t->c_cflag & CSTOPB), !(t->c_cflag & PARENB), (t->c_cflag & CRTSCTS), (t->c_iflag & IXON), (t->c_iflag & IXANY), t->c_cc[VSTART], t->c_cc[VSTOP]); splx (s); return 0; } #if __FreeBSD_version < 400000 static struct tty *cx_devtotty (dev_t dev) { int unit = UNIT (dev); if (unit == UNIT_CTL || unit >= NCX*NCHAN || ! channel[unit]) return 0; return &channel[unit]->tty; } #endif /* * Stop output on a line */ static void cx_stop (struct tty *tp, int flag) { drv_t *d = channel [UNIT (tp->t_dev)]; int s; if (!d) return; s = splhigh (); if (tp->t_state & TS_BUSY) { /* Stop transmitter */ CX_DEBUG2 (d, ("cx_stop\n")); cx_transmitter_ctl (d->chan, 0); } splx (s); } /* * Process the (delayed) carrier signal setup. */ static void cx_carrier (void *arg) { drv_t *d = arg; cx_chan_t *c = d->chan; int s, cd; s = splhigh (); cd = cx_get_cd (c); if (d->cd != cd) { if (cd) { CX_DEBUG (d, ("carrier on\n")); d->cd = 1; splx (s); ttyld_modem(&d->tty, 1); } else { CX_DEBUG (d, ("carrier loss\n")); d->cd = 0; splx (s); ttyld_modem(&d->tty, 0); } } } /* * Modem signal callback function. */ static void cx_modem (cx_chan_t *c) { drv_t *d = c->sys; if (!d || c->mode != M_ASYNC) return; /* Handle carrier detect/loss. */ untimeout (cx_carrier, c, d->dcd_timeout_handle); /* Carrier changed - delay processing DCD for a while * to give both sides some time to initialize. */ d->dcd_timeout_handle = timeout (cx_carrier, d, hz/2); } #if __FreeBSD_version < 400000 struct isa_driver cxdriver = { cx_probe, cx_attach, "cx" }; static struct cdevsw cx_cdevsw = { cx_open, cx_close, cx_read, cx_write, cx_ioctl, cx_stop, noreset, cx_devtotty, ttpoll, nommap, NULL, "cx", NULL, -1, }; #elif __FreeBSD_version < 500000 static struct cdevsw cx_cdevsw = { cx_open, cx_close, cx_read, cx_write, cx_ioctl, ttypoll, nommap, nostrategy, "cx", CDEV_MAJOR, nodump, nopsize, D_TTY, -1 }; #elif __FreeBSD_version == 500000 static struct cdevsw cx_cdevsw = { cx_open, cx_close, cx_read, cx_write, cx_ioctl, ttypoll, nommap, nostrategy, "cx", CDEV_MAJOR, nodump, nopsize, D_TTY, }; #elif __FreeBSD_version <= 501000 static struct cdevsw cx_cdevsw = { .d_open = cx_open, .d_close = cx_close, .d_read = cx_read, .d_write = cx_write, .d_ioctl = cx_ioctl, .d_poll = ttypoll, .d_mmap = nommap, .d_strategy = nostrategy, .d_name = "cx", .d_maj = CDEV_MAJOR, .d_dump = nodump, .d_flags = D_TTY, }; #elif __FreeBSD_version < 502103 static struct cdevsw cx_cdevsw = { .d_open = cx_open, .d_close = cx_close, .d_read = cx_read, .d_write = cx_write, .d_ioctl = cx_ioctl, .d_poll = ttypoll, .d_name = "cx", .d_maj = CDEV_MAJOR, .d_flags = D_TTY, }; #else /* __FreeBSD_version >= 502103 */ static struct cdevsw cx_cdevsw = { .d_version = D_VERSION, .d_open = cx_open, .d_close = cx_close, .d_read = cx_read, .d_write = cx_write, .d_ioctl = cx_ioctl, .d_name = "cx", .d_maj = CDEV_MAJOR, .d_flags = D_TTY | D_NEEDGIANT, }; #endif #ifdef NETGRAPH #if __FreeBSD_version >= 500000 static int ng_cx_constructor (node_p node) { drv_t *d = NG_NODE_PRIVATE (node); #else static int ng_cx_constructor (node_p *node) { drv_t *d = (*node)->private; #endif CX_DEBUG (d, ("Constructor\n")); return EINVAL; } static int ng_cx_newhook (node_p node, hook_p hook, const char *name) { int s; #if __FreeBSD_version >= 500000 drv_t *d = NG_NODE_PRIVATE (node); #else drv_t *d = node->private; #endif if (d->chan->mode == M_ASYNC) return EINVAL; /* Attach debug hook */ if (strcmp (name, NG_CX_HOOK_DEBUG) == 0) { #if __FreeBSD_version >= 500000 NG_HOOK_SET_PRIVATE (hook, NULL); #else hook->private = 0; #endif d->debug_hook = hook; return 0; } /* Check for raw hook */ if (strcmp (name, NG_CX_HOOK_RAW) != 0) return EINVAL; #if __FreeBSD_version >= 500000 NG_HOOK_SET_PRIVATE (hook, d); #else hook->private = d; #endif d->hook = hook; s = splhigh (); cx_up (d); splx (s); return 0; } static int print_modems (char *s, cx_chan_t *c, int need_header) { int status = cx_modem_status (c->sys); int length = 0; if (need_header) length += sprintf (s + length, " LE DTR DSR RTS CTS CD\n"); length += sprintf (s + length, "%4s %4s %4s %4s %4s %4s\n", status & TIOCM_LE ? "On" : "-", status & TIOCM_DTR ? "On" : "-", status & TIOCM_DSR ? "On" : "-", status & TIOCM_RTS ? "On" : "-", status & TIOCM_CTS ? "On" : "-", status & TIOCM_CD ? "On" : "-"); return length; } static int print_stats (char *s, cx_chan_t *c, int need_header) { int length = 0; if (need_header) length += sprintf (s + length, " Rintr Tintr Mintr Ibytes Ipkts Ierrs Obytes Opkts Oerrs\n"); length += sprintf (s + length, "%7ld %7ld %7ld %8ld %7ld %7ld %8ld %7ld %7ld\n", c->rintr, c->tintr, c->mintr, c->ibytes, c->ipkts, c->ierrs, c->obytes, c->opkts, c->oerrs); return length; } static int print_chan (char *s, cx_chan_t *c) { drv_t *d = c->sys; int length = 0; length += sprintf (s + length, "cx%d", c->board->num * NCHAN + c->num); if (d->chan->debug) length += sprintf (s + length, " debug=%d", d->chan->debug); if (cx_get_baud (c)) length += sprintf (s + length, " %ld", cx_get_baud (c)); else length += sprintf (s + length, " extclock"); if (c->mode == M_HDLC) { length += sprintf (s + length, " dpll=%s", cx_get_dpll (c) ? "on" : "off"); length += sprintf (s + length, " nrzi=%s", cx_get_nrzi (c) ? "on" : "off"); } length += sprintf (s + length, " loop=%s", cx_get_loop (c) ? "on\n" : "off\n"); return length; } #if __FreeBSD_version >= 500000 static int ng_cx_rcvmsg (node_p node, item_p item, hook_p lasthook) { drv_t *d = NG_NODE_PRIVATE (node); struct ng_mesg *msg; #else static int ng_cx_rcvmsg (node_p node, struct ng_mesg *msg, const char *retaddr, struct ng_mesg **rptr) { drv_t *d = node->private; #endif struct ng_mesg *resp = NULL; int error = 0; if (!d) return EINVAL; CX_DEBUG (d, ("Rcvmsg\n")); #if __FreeBSD_version >= 500000 NGI_GET_MSG (item, msg); #endif switch (msg->header.typecookie) { default: error = EINVAL; break; case NGM_CX_COOKIE: printf ("Don't forget to implement\n"); error = EINVAL; break; case NGM_GENERIC_COOKIE: switch (msg->header.cmd) { default: error = EINVAL; break; case NGM_TEXT_STATUS: { char *s; int l = 0; int dl = sizeof (struct ng_mesg) + 730; #if __FreeBSD_version >= 500000 NG_MKRESPONSE (resp, msg, dl, M_NOWAIT); if (! resp) { error = ENOMEM; break; } #else MALLOC (resp, struct ng_mesg *, dl, M_NETGRAPH, M_NOWAIT); if (! resp) { error = ENOMEM; break; } #endif bzero (resp, dl); s = (resp)->data; l += print_chan (s + l, d->chan); l += print_stats (s + l, d->chan, 1); l += print_modems (s + l, d->chan, 1); #if __FreeBSD_version < 500000 (resp)->header.version = NG_VERSION; (resp)->header.arglen = strlen (s) + 1; (resp)->header.token = msg->header.token; (resp)->header.typecookie = NGM_CX_COOKIE; (resp)->header.cmd = msg->header.cmd; #endif strncpy ((resp)->header.cmdstr, "status", NG_CMDSTRLEN); } break; } break; } #if __FreeBSD_version >= 500000 NG_RESPOND_MSG (error, node, item, resp); NG_FREE_MSG (msg); #else *rptr = resp; FREE (msg, M_NETGRAPH); #endif return error; } #if __FreeBSD_version >= 500000 static int ng_cx_rcvdata (hook_p hook, item_p item) { drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE(hook)); struct mbuf *m; meta_p meta; #else static int ng_cx_rcvdata (hook_p hook, struct mbuf *m, meta_p meta) { drv_t *d = hook->node->private; #endif struct ifqueue *q; int s; #if __FreeBSD_version >= 500000 NGI_GET_M (item, m); NGI_GET_META (item, meta); NG_FREE_ITEM (item); if (! NG_HOOK_PRIVATE (hook) || ! d) { NG_FREE_M (m); NG_FREE_META (meta); #else if (! hook->private || ! d) { NG_FREE_DATA (m,meta); #endif return ENETDOWN; } q = (meta && meta->priority > 0) ? &d->hi_queue : &d->lo_queue; s = splhigh (); #if __FreeBSD_version >= 500000 IF_LOCK (q); if (_IF_QFULL (q)) { _IF_DROP (q); IF_UNLOCK (q); splx (s); NG_FREE_M (m); NG_FREE_META (meta); return ENOBUFS; } _IF_ENQUEUE (q, m); IF_UNLOCK (q); #else if (IF_QFULL (q)) { IF_DROP (q); splx (s); NG_FREE_DATA (m, meta); return ENOBUFS; } IF_ENQUEUE (q, m); #endif cx_start (d); splx (s); return 0; } static int ng_cx_rmnode (node_p node) { #if __FreeBSD_version >= 500000 drv_t *d = NG_NODE_PRIVATE (node); CX_DEBUG (d, ("Rmnode\n")); if (d && d->running) { int s = splhigh (); cx_down (d); splx (s); } #ifdef KLD_MODULE if (node->nd_flags & NG_REALLY_DIE) { NG_NODE_SET_PRIVATE (node, NULL); NG_NODE_UNREF (node); } node->nd_flags &= ~NG_INVALID; #endif #else /* __FreeBSD_version < 500000 */ drv_t *d = node->private; int s; s = splhigh (); cx_down (d); splx (s); node->flags |= NG_INVALID; ng_cutlinks (node); #ifdef KLD_MODULE ng_unname (node); ng_unref (node); #else node->flags &= ~NG_INVALID; #endif #endif return 0; } static void ng_cx_watchdog (void *arg) { drv_t *d = arg; if (d->timeout == 1) cx_watchdog (d); if (d->timeout) d->timeout--; d->timeout_handle = timeout (ng_cx_watchdog, d, hz); } static int ng_cx_connect (hook_p hook) { #if __FreeBSD_version >= 500000 drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); #else drv_t *d = hook->node->private; #endif d->timeout_handle = timeout (ng_cx_watchdog, d, hz); return 0; } static int ng_cx_disconnect (hook_p hook) { #if __FreeBSD_version >= 500000 drv_t *d = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); #else drv_t *d = hook->node->private; #endif int s; s = splhigh (); #if __FreeBSD_version >= 500000 if (NG_HOOK_PRIVATE (hook)) #else if (hook->private) #endif cx_down (d); splx (s); untimeout (ng_cx_watchdog, d, d->timeout_handle); return 0; } #endif /*NETGRAPH*/ #ifdef KLD_MODULE #if __FreeBSD_version < 400000 /* * Function called when loading the driver. */ static int cx_load (void) { int i; for (i=0;iirq, 0, 0, (inthand2_t*) cx_intr, &net_imask, id.id_unit); enable_intr(); } if (!i) { /* Deactivate the timeout routine. And soft interrupt*/ untimeout (cx_timeout, 0, timeout_handle); unregister_swi (SWI_TTY, cx_softintr); return ENXIO; } return 0; } /* * Function called when unloading the driver. */ static int cx_unload (void) { int i, s; /* Check if the device is busy (open). */ for (i=0; ichan)->type == T_NONE) continue; if (d->lock) return EBUSY; if (c->mode == M_ASYNC && (d->tty.t_state & TS_ISOPEN) && (d->open_dev|0x2)) return EBUSY; if (d->running) return EBUSY; } s = splhigh (); /* Deactivate the timeout routine. And soft interrupt*/ for (i=0; iport) continue; untimeout (cx_timeout, 0, timeout_handle); unregister_swi (SWI_TTY, cx_softintr); break; } for (i=0; ichan)->type == T_NONE) continue; if (d->dtr_timeout_handle.callout) untimeout (cx_dtrwakeup, d, d->dtr_timeout_handle); if (d->dcd_timeout_handle.callout) untimeout (cx_carrier, c, d->dcd_timeout_handle); } /* Close all active boards. */ for (i=0; iport) continue; cx_close_board (b); } for (i=0; iport) continue; if (led_timo[i].callout) untimeout (cx_led_off, b, led_timo[i]); } /* OK to unload the driver, unregister the interrupt first. */ for (i=0; iport) continue; /* Disable the interrupt request. */ disable_intr(); unregister_intr (b->irq, (inthand2_t *)cx_intr); isa_dma_release (b->dma); enable_intr(); } splx (s); s = splhigh (); /* Detach the interfaces, free buffer memory. */ for (i=0; ichan)->type == T_NONE) continue; #ifndef NETGRAPH #if NBPFILTER > 0 /* Detach from the packet filter list of interfaces. */ { struct bpf_if *q, **b = &bpf_iflist; while ((q = *b)) { if (q->bif_ifp == d->pp.pp_if) { *b = q->bif_next; free (q, M_DEVBUF); } b = &(q->bif_next); } } #endif /* NBPFILTER */ /* Detach from the sync PPP list. */ sppp_detach (&d->pp.pp_if); /* Detach from the system list of interfaces. */ { struct ifaddr *ifa; TAILQ_FOREACH (ifa, &d->pp.pp_if.if_addrhead, ifa_link) { TAILQ_REMOVE (&d->pp.pp_if.if_addrhead, ifa, ifa_link); free (ifa, M_IFADDR); } TAILQ_REMOVE (&ifnet, &d->pp.pp_if, if_link); } #endif /* !NETGRAPH */ /* Deallocate buffers. */ /* free (d, M_DEVBUF);*/ } for (i=0; inum] = 0; free (b, M_DEVBUF); } splx (s); return 0; } #define devsw(a) cdevsw[major((a))] #endif /* __FreeBSD_version < 400000 */ #endif /* KLD_MODULE */ #if __FreeBSD_version < 400000 #ifdef KLD_MODULE static int cx_modevent (module_t mod, int type, void *unused) { dev_t dev; int result; static int load_count = 0; dev = makedev (CDEV_MAJOR, 0); switch (type) { case MOD_LOAD: if (devsw(dev)) return (ENXIO); load_count ++; cdevsw_add (&dev, &cx_cdevsw, NULL); timeout_handle = timeout (cx_timeout, 0, hz*5); /* Software interrupt. */ register_swi (SWI_TTY, cx_softintr); result = cx_load (); return result; case MOD_UNLOAD: result = cx_unload (); if (result) return result; if (devsw(dev)&&!(load_count-1)) { cdevsw_add (&dev, NULL, NULL); } load_count --; return result; case MOD_SHUTDOWN: break; } return 0; } #endif /* KLD_MODULE */ #else /* __FreeBSD_version >= 400000 */ static int cx_modevent (module_t mod, int type, void *unused) { dev_t dev; static int load_count = 0; struct cdevsw *cdsw; #if __FreeBSD_version >= 502103 dev = udev2dev (makeudev(CDEV_MAJOR, 0)); #else dev = makedev (CDEV_MAJOR, 0); #endif switch (type) { case MOD_LOAD: if (dev != NODEV && (cdsw = devsw (dev)) && cdsw->d_maj == CDEV_MAJOR) { printf ("Sigma driver is already in system\n"); return (EEXIST); } #if __FreeBSD_version >= 500000 && defined NETGRAPH if (ng_newtype (&typestruct)) printf ("Failed to register ng_cx\n"); #endif ++load_count; #if __FreeBSD_version <= 500000 cdevsw_add (&cx_cdevsw); #endif timeout_handle = timeout (cx_timeout, 0, hz*5); /* Software interrupt. */ #if __FreeBSD_version < 500000 register_swi (SWI_TTY, cx_softintr); #else swi_add(&tty_ithd, "tty:cx", cx_softintr, NULL, SWI_TTY, 0, &cx_fast_ih); #endif break; case MOD_UNLOAD: if (load_count == 1) { printf ("Removing device entry for Sigma\n"); #if __FreeBSD_version <= 500000 cdevsw_remove (&cx_cdevsw); #endif #if __FreeBSD_version >= 500000 && defined NETGRAPH ng_rmtype (&typestruct); #endif } if (timeout_handle.callout) untimeout (cx_timeout, 0, timeout_handle); #if __FreeBSD_version >= 500000 ithread_remove_handler (cx_fast_ih); #else unregister_swi (SWI_TTY, cx_softintr); #endif --load_count; break; case MOD_SHUTDOWN: break; } return 0; } #endif /* __FreeBSD_version >= 400000 */ #ifdef NETGRAPH static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_CX_NODE_TYPE, .constructor = ng_cx_constructor, .rcvmsg = ng_cx_rcvmsg, .shutdown = ng_cx_rmnode, .newhook = ng_cx_newhook, .connect = ng_cx_connect, .rcvdata = ng_cx_rcvdata, .disconnect = ng_cx_disconnect }; #if __FreeBSD_version < 400000 NETGRAPH_INIT_ORDERED (cx, &typestruct, SI_SUB_DRIVERS,\ SI_ORDER_MIDDLE + CDEV_MAJOR); #endif #endif /*NETGRAPH*/ #if __FreeBSD_version >= 500000 #ifdef NETGRAPH MODULE_DEPEND (ng_cx, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); #else MODULE_DEPEND (isa_cx, sppp, 1, 1, 1); #endif #ifdef KLD_MODULE DRIVER_MODULE (cxmod, isa, cx_isa_driver, cx_devclass, cx_modevent, NULL); #else DRIVER_MODULE (cx, isa, cx_isa_driver, cx_devclass, cx_modevent, NULL); #endif #elif __FreeBSD_version >= 400000 #ifdef NETGRAPH DRIVER_MODULE(cx, isa, cx_isa_driver, cx_devclass, ng_mod_event, &typestruct); #else DRIVER_MODULE(cx, isa, cx_isa_driver, cx_devclass, cx_modevent, 0); #endif #else /* __FreeBSD_version < 400000 */ #ifdef KLD_MODULE #ifndef NETGRAPH static moduledata_t cxmod = { "cx", cx_modevent, NULL}; DECLARE_MODULE (cx, cxmod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE + CDEV_MAJOR); #endif #else /* KLD_MODULE */ /* * Now for some driver initialisation. * Occurs ONCE during boot (very early). * This is if we are NOT a loadable module. */ static void cx_drvinit (void *unused) { #if __FreeBSD_version < 400000 dev_t dev; dev = makedev (CDEV_MAJOR, 0); cdevsw_add (&dev, &cx_cdevsw, NULL); #else cdevsw_add (&cx_cdevsw); #endif /* Activate the timeout routine. */ timeout_handle = timeout (cx_timeout, 0, hz*5); /* Software interrupt. */ register_swi (SWI_TTY, cx_softintr); #ifdef NETGRAPH #if 0 /* Register our node type in netgraph */ if (ng_newtype (&typestruct)) printf ("Failed to register ng_cx\n"); #endif #endif } SYSINIT (cxdev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE+CDEV_MAJOR, cx_drvinit, 0) #endif /* KLD_MODULE */ #endif /* __FreeBSD_version < 400000 */ #endif /* NCX */ diff --git a/sys/dev/cy/cy.c b/sys/dev/cy/cy.c index 58abf6a24326..9dc95b3e69ea 100644 --- a/sys/dev/cy/cy.c +++ b/sys/dev/cy/cy.c @@ -1,2889 +1,2875 @@ /*- * cyclades cyclom-y serial driver * Andrew Herbert , 17 August 1993 * * Copyright (c) 1993 Andrew Herbert. * 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. The name Andrew Herbert may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY ``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 I 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_compat.h" /* * TODO: * Atomic COR change. * Consoles. */ /* * Temporary compile-time configuration options. */ #define RxFifoThreshold (CD1400_RX_FIFO_SIZE / 2) /* Number of chars in the receiver FIFO before an * an interrupt is generated. Should depend on * line speed. Needs to be about 6 on a 486DX33 * for 4 active ports at 115200 bps. Why doesn't * 10 work? */ #define PollMode /* Use polling-based irq service routine, not the * hardware svcack lines. Must be defined for * Cyclom-16Y boards. Less efficient for Cyclom-8Ys, * and stops 4 * 115200 bps from working. */ #undef Smarts /* Enable slightly more CD1400 intelligence. Mainly * the output CR/LF processing, plus we can avoid a * few checks usually done in ttyinput(). * * XXX not fully implemented, and not particularly * worthwhile. */ #undef CyDebug /* Include debugging code (not very expensive). */ /* These will go away. */ #undef SOFT_CTS_OFLOW #define SOFT_HOTCHAR #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NCY 10 /* KLUDGE */ /* * Dictionary so that I can name everything *sio* or *com* to compare with * sio.c. There is also lots of ugly formatting and unnecessary ifdefs to * simplify the comparision. These will go away. */ #define LSR_BI CD1400_RDSR_BREAK #define LSR_FE CD1400_RDSR_FE #define LSR_OE CD1400_RDSR_OE #define LSR_PE CD1400_RDSR_PE #define MCR_DTR CD1400_MSVR2_DTR #define MCR_RTS CD1400_MSVR1_RTS #define MSR_CTS CD1400_MSVR2_CTS #define MSR_DCD CD1400_MSVR2_CD #define MSR_DSR CD1400_MSVR2_DSR #define MSR_RI CD1400_MSVR2_RI #define NSIO (NCY * CY_MAX_PORTS) #define comconsole cyconsole #define comdefaultrate cydefaultrate #define com_events cy_events #define comhardclose cyhardclose #define commctl cymctl #define comparam cyparam #define comspeed cyspeed #define comstart cystart #define comwakeup cywakeup #define p_com_addr p_cy_addr #define sioclose cyclose #define siodriver cydriver #define siodtrwakeup cydtrwakeup #define sioinput cyinput #define siointr1 cyintr #define sioioctl cyioctl #define sioopen cyopen #define siopoll cypoll #define siosettimeout cysettimeout #define siosetwater cysetwater #define comstop cystop #define siowrite cywrite #define sio_fast_ih cy_fast_ih #define sio_inited cy_inited #define sio_irec cy_irec #define sio_lock cy_lock #define sio_slow_ih cy_slow_ih #define sio_timeout cy_timeout #define sio_timeout_handle cy_timeout_handle #define sio_timeouts_until_log cy_timeouts_until_log #define CY_MAX_PORTS (CD1400_NO_OF_CHANNELS * CY_MAX_CD1400s) /* We encode the cyclom unit number (cyu) in spare bits in the IVR's. */ #define CD1400_xIVR_CHAN_SHIFT 3 #define CD1400_xIVR_CHAN 0x1F /* * ETC states. com->etc may also contain a hardware ETC command value, * meaning that execution of that command is pending. */ #define ETC_NONE 0 /* we depend on bzero() setting this */ #define ETC_BREAK_STARTING 1 #define ETC_BREAK_STARTED 2 #define ETC_BREAK_ENDING 3 #define ETC_BREAK_ENDED 4 #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) #define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) /* * Not all of the magic is parametrized in the following macros. 16 and * 0xff are related to the bitfields in a udev_t. CY_MAX_PORTS must be * ((0xff & ~MINOR_MAGIC_MASK) + 1) for things to work. */ #define MINOR_TO_UNIT(mynor) (((mynor) >> 16) * CY_MAX_PORTS \ | (((mynor) & 0xff) & ~MINOR_MAGIC_MASK)) #define UNIT_TO_MINOR(unit) (((unit) / CY_MAX_PORTS) << 16 \ | (((unit) & 0xff) & ~MINOR_MAGIC_MASK)) /* * com state bits. * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher * than the other bits so that they can be tested as a group without masking * off the low bits. * * The following com and tty flags correspond closely: * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and * comstop()) * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ #define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ #define CSE_ODONE 1 /* output transmitted */ static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 "interrupt-level buffer overflow", #define CE_TTY_BUF_OVERFLOW 2 "tty-level buffer overflow", }; #define CE_NTYPES 3 #define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) #ifdef SMP #define COM_LOCK() mtx_lock_spin(&sio_lock) #define COM_UNLOCK() mtx_unlock_spin(&sio_lock) #else #define COM_LOCK() #define COM_UNLOCK() #endif /* types. XXX - should be elsewhere */ typedef u_char bool_t; /* boolean */ /* queue of linear buffers */ struct lbq { u_char *l_head; /* next char to process */ u_char *l_tail; /* one past the last char to process */ struct lbq *l_next; /* next in queue */ bool_t l_queued; /* nonzero if queued */ }; /* com device structure */ struct com_s { u_char state; /* miscellaneous flag bits */ bool_t active_out; /* nonzero if the callout device is open */ #if 0 u_char cfcr_image; /* copy of value written to CFCR */ #endif u_char etc; /* pending Embedded Transmit Command */ u_char extra_state; /* more flag bits, separate for order trick */ #if 0 u_char fifo_image; /* copy of value written to FIFO */ #endif u_char gfrcr_image; /* copy of value read from GFRCR */ #if 0 bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t loses_outints; /* nonzero if device loses output interrupts */ #endif u_char mcr_dtr; /* MCR bit that is wired to DTR */ u_char mcr_image; /* copy of value written to MCR */ u_char mcr_rts; /* MCR bit that is wired to RTS */ #if 0 #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ bool_t no_irq; /* nonzero if irq is not attached */ bool_t poll; /* nonzero if polling is required */ bool_t poll_output; /* nonzero if polling for output is required */ #endif int unit; /* unit number */ int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ #if 0 u_int tx_fifo_size; #endif u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly * because there would be too many side effects to handle conveniently. * Instead, it reads copies of the registers stored here by the * interrupt handler. */ u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ibufold; /* old input buffer, to be freed */ u_char *ihighwater; /* threshold in input buffer */ u_char *iptr; /* next free spot in input buffer */ int ibufsize; /* size of ibuf (not include error bytes) */ int ierroff; /* offset of error bytes in ibuf */ struct lbq obufq; /* head of queue of output buffers */ struct lbq obufs[2]; /* output buffers */ int cy_align; /* index for register alignment */ cy_addr cy_iobase; /* base address of this port's cyclom */ cy_addr iobase; /* base address of this port's cd1400 */ int mcr_rts_reg; /* cd1400 reg number of reg holding mcr_rts */ struct tty *tp; /* cross reference */ /* Initial state. */ struct termios it_in; /* should be in struct tty */ struct termios it_out; /* Lock state. */ struct termios lt_in; /* should be in struct tty */ struct termios lt_out; bool_t do_timestamp; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_timestamp; u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; u_long error_counts[CE_NTYPES]; u_int recv_exception; /* exception chars received */ u_int mdm; /* modem signal changes */ #ifdef CyDebug u_int start_count; /* no. of calls to comstart() */ u_int start_real; /* no. of calls that did something */ #endif u_char car; /* CD1400 CAR shadow (if first unit in cd) */ u_char channel_control;/* CD1400 CCR control command shadow */ u_char cor[3]; /* CD1400 COR1-3 shadows */ u_char intr_enable; /* CD1400 SRER shadow */ /* * Data area for output buffers. Someday we should build the output * buffer queue without copying data. */ u_char obuf1[256]; u_char obuf2[256]; }; devclass_t cy_devclass; char cy_driver_name[] = "cy"; static void cd1400_channel_cmd(struct com_s *com, int cmd); static void cd1400_channel_cmd_wait(struct com_s *com); static void cd_etc(struct com_s *com, int etc); static int cd_getreg(struct com_s *com, int reg); static void cd_setreg(struct com_s *com, int reg, int val); static timeout_t siodtrwakeup; static void comhardclose(struct com_s *com); static void sioinput(struct com_s *com); static int commctl(struct com_s *com, int bits, int how); static int comparam(struct tty *tp, struct termios *t); static void siopoll(void *arg); static void siosettimeout(void); static int siosetwater(struct com_s *com, speed_t speed); static int comspeed(speed_t speed, u_long cy_clock, int *prescaler_io); static void comstart(struct tty *tp); static void comstop(struct tty *tp, int rw); static timeout_t comwakeup; static void disc_optim(struct tty *tp, struct termios *t, struct com_s *com); #ifdef CyDebug void cystatus(int unit); #endif static struct mtx sio_lock; static int sio_inited; /* table and macro for fast conversion from a unit number to its com struct */ static struct com_s *p_com_addr[NSIO]; #define com_addr(unit) (p_com_addr[unit]) static d_open_t sioopen; static d_close_t sioclose; static d_write_t siowrite; static d_ioctl_t sioioctl; static struct cdevsw sio_cdevsw = { .d_version = D_VERSION, .d_open = sioopen, .d_close = sioclose, .d_write = siowrite, .d_ioctl = sioioctl, .d_name = cy_driver_name, .d_flags = D_TTY | D_NEEDGIANT, }; static int comconsole = -1; static speed_t comdefaultrate = TTYDEF_SPEED; static u_int com_events; /* input chars + weighted output completions */ static void *sio_fast_ih; static void *sio_slow_ih; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); #ifdef CyDebug static u_int cd_inbs; static u_int cy_inbs; static u_int cd_outbs; static u_int cy_outbs; static u_int cy_svrr_probes; static u_int cy_timeouts; #endif static int cy_chip_offset[] = { 0x0000, 0x0400, 0x0800, 0x0c00, 0x0200, 0x0600, 0x0a00, 0x0e00, }; static int cy_nr_cd1400s[NCY]; static int cy_total_devices; #undef RxFifoThreshold static int volatile RxFifoThreshold = (CD1400_RX_FIFO_SIZE / 2); int cy_units(cy_iobase, cy_align) cy_addr cy_iobase; int cy_align; { int cyu; u_char firmware_version; int i; cy_addr iobase; for (cyu = 0; cyu < CY_MAX_CD1400s; ++cyu) { iobase = cy_iobase + (cy_chip_offset[cyu] << cy_align); /* wait for chip to become ready for new command */ for (i = 0; i < 10; i++) { DELAY(50); if (!cd_inb(iobase, CD1400_CCR, cy_align)) break; } /* clear the GFRCR register */ cd_outb(iobase, CD1400_GFRCR, cy_align, 0); /* issue a reset command */ cd_outb(iobase, CD1400_CCR, cy_align, CD1400_CCR_CMDRESET | CD1400_CCR_FULLRESET); /* XXX bogus initialization to avoid a gcc bug/warning. */ firmware_version = 0; /* wait for the CD1400 to initialize itself */ for (i = 0; i < 200; i++) { DELAY(50); /* retrieve firmware version */ firmware_version = cd_inb(iobase, CD1400_GFRCR, cy_align); if ((firmware_version & 0xf0) == 0x40) break; } /* * Anything in the 0x40-0x4F range is fine. * If one CD1400 is bad then we don't support higher * numbered good ones on this board. */ if ((firmware_version & 0xf0) != 0x40) break; } return (cyu); } void * cyattach_common(cy_iobase, cy_align) cy_addr cy_iobase; int cy_align; { int adapter; int cyu; u_char firmware_version; cy_addr iobase; int minorbase; int ncyu; int unit; while (sio_inited != 2) if (atomic_cmpset_int(&sio_inited, 0, 1)) { mtx_init(&sio_lock, cy_driver_name, NULL, MTX_SPIN); atomic_store_rel_int(&sio_inited, 2); } adapter = cy_total_devices; if ((u_int)adapter >= NCY) { printf( "cy%d: can't attach adapter: insufficient cy devices configured\n", adapter); return (NULL); } ncyu = cy_units(cy_iobase, cy_align); if (ncyu == 0) return (NULL); cy_nr_cd1400s[adapter] = ncyu; cy_total_devices++; unit = adapter * CY_MAX_PORTS; for (cyu = 0; cyu < ncyu; ++cyu) { int cdu; iobase = (cy_addr) (cy_iobase + (cy_chip_offset[cyu] << cy_align)); firmware_version = cd_inb(iobase, CD1400_GFRCR, cy_align); /* Set up a receive timeout period of than 1+ ms. */ cd_outb(iobase, CD1400_PPR, cy_align, howmany(CY_CLOCK(firmware_version) / CD1400_PPR_PRESCALER, 1000)); for (cdu = 0; cdu < CD1400_NO_OF_CHANNELS; ++cdu, ++unit) { struct com_s *com; int s; com = malloc(sizeof *com, M_DEVBUF, M_NOWAIT | M_ZERO); if (com == NULL) break; com->unit = unit; com->gfrcr_image = firmware_version; if (CY_RTS_DTR_SWAPPED(firmware_version)) { com->mcr_dtr = MCR_RTS; com->mcr_rts = MCR_DTR; com->mcr_rts_reg = CD1400_MSVR2; } else { com->mcr_dtr = MCR_DTR; com->mcr_rts = MCR_RTS; com->mcr_rts_reg = CD1400_MSVR1; } com->dtr_wait = 3 * hz; com->obufs[0].l_head = com->obuf1; com->obufs[1].l_head = com->obuf2; com->cy_align = cy_align; com->cy_iobase = cy_iobase; com->iobase = iobase; com->car = ~CD1400_CAR_CHAN; /* * We don't use all the flags from 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. */ com->it_in.c_iflag = 0; com->it_in.c_oflag = 0; com->it_in.c_cflag = TTYDEF_CFLAG; com->it_in.c_lflag = 0; if (unit == comconsole) { com->it_in.c_iflag = TTYDEF_IFLAG; com->it_in.c_oflag = TTYDEF_OFLAG; com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; com->it_in.c_lflag = TTYDEF_LFLAG; com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; } if (siosetwater(com, com->it_in.c_ispeed) != 0) { free(com, M_DEVBUF); return (NULL); } termioschars(&com->it_in); com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; com->it_out = com->it_in; s = spltty(); com_addr(unit) = com; splx(s); if (sio_fast_ih == NULL) { swi_add(&tty_ithd, "tty:cy", siopoll, NULL, SWI_TTY, 0, &sio_fast_ih); swi_add(&clk_ithd, "tty:cy", siopoll, NULL, SWI_TTY, 0, &sio_slow_ih); } minorbase = UNIT_TO_MINOR(unit); make_dev(&sio_cdevsw, minorbase, UID_ROOT, GID_WHEEL, 0600, "ttyc%r%r", adapter, unit % CY_MAX_PORTS); make_dev(&sio_cdevsw, minorbase | CONTROL_INIT_STATE, UID_ROOT, GID_WHEEL, 0600, "ttyic%r%r", adapter, unit % CY_MAX_PORTS); make_dev(&sio_cdevsw, minorbase | CONTROL_LOCK_STATE, UID_ROOT, GID_WHEEL, 0600, "ttylc%r%r", adapter, unit % CY_MAX_PORTS); make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK, UID_UUCP, GID_DIALER, 0660, "cuac%r%r", adapter, unit % CY_MAX_PORTS); make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK | CONTROL_INIT_STATE, UID_UUCP, GID_DIALER, 0660, "cuaic%r%r", adapter, unit % CY_MAX_PORTS); make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK | CONTROL_LOCK_STATE, UID_UUCP, GID_DIALER, 0660, "cualc%r%r", adapter, unit % CY_MAX_PORTS); } } /* ensure an edge for the next interrupt */ cy_outb(cy_iobase, CY_CLEAR_INTR, cy_align, 0); return (com_addr(adapter * CY_MAX_PORTS)); } static int sioopen(dev, flag, mode, td) dev_t dev; int flag; int mode; struct thread *td; { struct com_s *com; int error; int mynor; int s; struct tty *tp; int unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIO || (com = com_addr(unit)) == NULL) return (ENXIO); if (mynor & CONTROL_MASK) return (0); tp = dev->si_tty = com->tp = ttymalloc(com->tp); s = spltty(); /* * We jump to this label after all non-interrupted sleeps to pick * up any changes of the device state. */ open_top: while (com->state & CS_DTR_OFF) { error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "cydtr", 0); if (error != 0) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!com->active_out) { error = EBUSY; goto out; } } else { if (com->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&com->active_out, TTIPRI | PCATCH, "cybi", 0); if (error != 0) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && suser(td)) { 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 = comstart; tp->t_stop = comstop; tp->t_param = comparam; tp->t_dev = dev; tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in; /* Encode per-board unit in LIVR for access in intr routines. */ cd_setreg(com, CD1400_LIVR, (unit & CD1400_xIVR_CHAN) << CD1400_xIVR_CHAN_SHIFT); (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); #if 0 com->poll = com->no_irq; com->poll_output = com->loses_outints; #endif ++com->wopeners; error = comparam(tp, &tp->t_termios); --com->wopeners; if (error != 0) goto out; #if 0 if (com->hasfifo) { /* * (Re)enable and flush fifos. * * Certain SMC chips cause problems if the fifos * are enabled while input is ready. Turn off the * fifo if necessary to clear the input. We test * the input ready bit after enabling the fifos * since we've already enabled them in comparam() * and to handle races between enabling and fresh * input. */ while (TRUE) { outb(iobase + com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image); DELAY(100); if (!(inb(com->line_status_port) & LSR_RXRDY)) break; outb(iobase + com_fifo, 0); DELAY(100); (void) inb(com->data_port); } } critical_enter(); COM_LOCK(); (void) inb(com->line_status_port); (void) inb(com->data_port); com->prev_modem_status = com->last_modem_status = inb(com->modem_status_port); outb(iobase + com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC); COM_UNLOCK(); critical_exit(); #else /* !0 */ /* * Flush fifos. This requires a full channel reset which * also disables the transmitter and receiver. Recover * from this. */ cd1400_channel_cmd(com, CD1400_CCR_CMDRESET | CD1400_CCR_CHANRESET); cd1400_channel_cmd(com, com->channel_control); critical_enter(); COM_LOCK(); com->prev_modem_status = com->last_modem_status = cd_getreg(com, CD1400_MSVR2); cd_setreg(com, CD1400_SRER, com->intr_enable = CD1400_SRER_MDMCH | CD1400_SRER_RXDATA); COM_UNLOCK(); critical_exit(); #endif /* 0 */ /* * Handle initial DCD. Callout devices get a fake initial * DCD (trapdoor DCD). If we are callout, then any sleeping * callin opens get woken up and resume sleeping on "cybi" * instead of "cydcd". */ /* * XXX `mynor & CALLOUT_MASK' should be * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where * TRAPDOOR_CARRIER is the default initial state for callout * devices and SOFT_CARRIER is like CLOCAL except it hides * the true carrier. */ if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) ttyld_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)) { ++com->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "cydcd", 0); --com->wopeners; if (error != 0) goto out; goto open_top; } error = ttyld_open(tp, dev); disc_optim(tp, &tp->t_termios, com); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) com->active_out = TRUE; siosettimeout(); out: splx(s); if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); return (error); } static int sioclose(dev, flag, mode, td) dev_t dev; int flag; int mode; struct thread *td; { struct com_s *com; int mynor; int s; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (0); com = com_addr(MINOR_TO_UNIT(mynor)); tp = com->tp; s = spltty(); cd_etc(com, CD1400_ETC_STOPBREAK); ttyld_close(tp, flag); disc_optim(tp, &tp->t_termios, com); comhardclose(com); ttyclose(tp); siosettimeout(); splx(s); #ifdef broken /* session holds a ref to the tty; can't deallocate */ ttyfree(tp); com->tp = NULL; #endif return (0); } static void comhardclose(com) struct com_s *com; { cy_addr iobase; int s; struct tty *tp; int unit; unit = com->unit; iobase = com->iobase; s = spltty(); #if 0 com->poll = FALSE; com->poll_output = FALSE; #endif com->do_timestamp = 0; #if 0 outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); #else /* XXX */ critical_enter(); COM_LOCK(); com->etc = ETC_NONE; cd_setreg(com, CD1400_COR2, com->cor[1] &= ~CD1400_COR2_ETC); COM_UNLOCK(); critical_exit(); cd1400_channel_cmd(com, CD1400_CCR_CMDRESET | CD1400_CCR_FTF); #endif { #if 0 outb(iobase + com_ier, 0); #else critical_enter(); COM_LOCK(); cd_setreg(com, CD1400_SRER, com->intr_enable = 0); COM_UNLOCK(); critical_exit(); #endif tp = com->tp; if ((tp->t_cflag & HUPCL) /* * XXX we will miss any carrier drop between here and the * next open. Perhaps we should watch DCD even when the * port is closed; it is not sufficient to check it at * the next open because it might go up and down while * we're not watching. */ || (!com->active_out && !(com->prev_modem_status & MSR_DCD) && !(com->it_in.c_cflag & CLOCAL)) || !(tp->t_state & TS_ISOPEN)) { (void)commctl(com, TIOCM_DTR, DMBIC); /* Disable receiver (leave transmitter enabled). */ com->channel_control = CD1400_CCR_CMDCHANCTL | CD1400_CCR_XMTEN | CD1400_CCR_RCVDIS; cd1400_channel_cmd(com, com->channel_control); if (com->dtr_wait != 0 && !(com->state & CS_DTR_OFF)) { timeout(siodtrwakeup, com, com->dtr_wait); com->state |= CS_DTR_OFF; } } } #if 0 if (com->hasfifo) { /* * Disable fifos so that they are off after controlled * reboots. Some BIOSes fail to detect 16550s when the * fifos are enabled. */ outb(iobase + com_fifo, 0); } #endif com->active_out = FALSE; wakeup(&com->active_out); wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } static int siowrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); tp = com_addr(unit)->tp; /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that * is not the console. In that situation we don't need/want the X * server taking over the console. */ if (constty != NULL && unit == comconsole) constty = NULL; #ifdef Smarts /* XXX duplicate ttwrite(), but without so much output processing on * CR & LF chars. Hardly worth the effort, given that high-throughput * sessions are raw anyhow. */ #else return (ttyld_write(tp, uio, flag)); #endif } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } /* * This function: * a) needs to be called with COM_LOCK() held, and * b) needs to return with COM_LOCK() held. */ static void sioinput(com) struct com_s *com; { u_char *buf; int incc; u_char line_status; int recv_data; struct tty *tp; buf = com->ibuf; tp = com->tp; if (!(tp->t_state & TS_ISOPEN)) { com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; return; } if (tp->t_state & TS_CAN_BYPASS_L_RINT) { /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ do { /* * This may look odd, but it is using save-and-enable * semantics instead of the save-and-disable semantics * that are used everywhere else. */ COM_UNLOCK(); critical_exit(); incc = com->iptr - buf; if (tp->t_rawq.c_cc + incc > tp->t_ihiwat && (com->state & CS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); com->delta_error_counts[CE_TTY_BUF_OVERFLOW] += b_to_q((char *)buf, incc, &tp->t_rawq); buf += incc; tk_nin += incc; tk_rawcc += incc; tp->t_rawcc += incc; ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; comstart(tp); } critical_enter(); COM_LOCK(); } while (buf < com->iptr); } else { do { /* * This may look odd, but it is using save-and-enable * semantics instead of the save-and-disable semantics * that are used everywhere else. */ COM_UNLOCK(); critical_exit(); line_status = buf[com->ierroff]; recv_data = *buf++; if (line_status & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { if (line_status & LSR_BI) recv_data |= TTY_BI; if (line_status & LSR_FE) recv_data |= TTY_FE; if (line_status & LSR_OE) recv_data |= TTY_OE; if (line_status & LSR_PE) recv_data |= TTY_PE; } ttyld_rint(tp, recv_data); critical_enter(); COM_LOCK(); } while (buf < com->iptr); } com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; /* * There is now room for another low-level buffer full of input, * so enable RTS if it is now disabled and there is room in the * high-level buffer. */ if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & com->mcr_rts) && !(tp->t_state & TS_TBLOCK)) #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else cd_setreg(com, com->mcr_rts_reg, com->mcr_image |= com->mcr_rts); #endif } void siointr1(vcom) void *vcom; { struct com_s *basecom; int baseu; int cy_align; cy_addr cy_iobase; int cyu; cy_addr iobase; u_char status; int unit; COM_LOCK(); /* XXX could this be placed down lower in the loop? */ basecom = (struct com_s *)vcom; baseu = basecom->unit; cy_align = basecom->cy_align; cy_iobase = basecom->cy_iobase; unit = baseu / CY_MAX_PORTS; /* check each CD1400 in turn */ for (cyu = 0; cyu < cy_nr_cd1400s[unit]; ++cyu) { iobase = (cy_addr) (cy_iobase + (cy_chip_offset[cyu] << cy_align)); /* poll to see if it has any work */ status = cd_inb(iobase, CD1400_SVRR, cy_align); if (status == 0) continue; #ifdef CyDebug ++cy_svrr_probes; #endif /* service requests as appropriate, giving priority to RX */ if (status & CD1400_SVRR_RXRDY) { struct com_s *com; u_int count; u_char *ioptr; u_char line_status; u_char recv_data; u_char serv_type; #ifdef PollMode u_char save_rir; #endif #ifdef PollMode save_rir = cd_inb(iobase, CD1400_RIR, cy_align); /* enter rx service */ cd_outb(iobase, CD1400_CAR, cy_align, save_rir); com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS)->car = save_rir & CD1400_CAR_CHAN; serv_type = cd_inb(iobase, CD1400_RIVR, cy_align); com = com_addr(baseu + ((serv_type >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #else /* ack receive service */ serv_type = cy_inb(iobase, CY8_SVCACKR, cy_align); com = com_addr(baseu + + ((serv_type >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif if (serv_type & CD1400_RIVR_EXCEPTION) { ++com->recv_exception; line_status = cd_inb(iobase, CD1400_RDSR, cy_align); /* break/unnattached error bits or real input? */ recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifndef SOFT_HOTCHAR if (line_status & CD1400_RDSR_SPECIAL && com->hotchar != 0) swi_sched(sio_fast_ih, 0); #endif #if 1 /* XXX "intelligent" PFO error handling would break O error handling */ if (line_status & (LSR_PE|LSR_FE|LSR_BI)) { /* Don't store PE if IGNPAR and BI if IGNBRK, this hack allows "raw" tty optimization works even if IGN* is set. */ if ( com->tp == NULL || !(com->tp->t_state & TS_ISOPEN) || ((line_status & (LSR_PE|LSR_FE)) && (com->tp->t_iflag & IGNPAR)) || ((line_status & LSR_BI) && (com->tp->t_iflag & IGNBRK))) goto cont; if ( (line_status & (LSR_PE|LSR_FE)) && (com->tp->t_state & TS_CAN_BYPASS_L_RINT) && ((line_status & LSR_FE) || ((line_status & LSR_PE) && (com->tp->t_iflag & INPCK)))) recv_data = 0; } #endif /* 1 */ ++com->bytes_in; #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) swi_sched(sio_fast_ih, 0); #endif ioptr = com->iptr; if (ioptr >= com->ibufend) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { if (com->do_timestamp) microtime(&com->timestamp); ++com_events; ioptr[0] = recv_data; ioptr[com->ierroff] = line_status; com->iptr = ++ioptr; if (ioptr == com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, com->mcr_rts_reg, cy_align, com->mcr_image &= ~com->mcr_rts); #endif if (line_status & LSR_OE) CE_RECORD(com, CE_OVERRUN); } goto cont; } else { int ifree; count = cd_inb(iobase, CD1400_RDCR, cy_align); if (!count) goto cont; com->bytes_in += count; ioptr = com->iptr; ifree = com->ibufend - ioptr; if (count > ifree) { count -= ifree; com_events += ifree; if (ifree != 0) { if (com->do_timestamp) microtime(&com->timestamp); do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) swi_sched(sio_fast_ih, 0); #endif ioptr[0] = recv_data; ioptr[com->ierroff] = 0; ++ioptr; } while (--ifree != 0); } com->delta_error_counts [CE_INTERRUPT_BUF_OVERFLOW] += count; do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) swi_sched(sio_fast_ih, 0); #endif } while (--count != 0); } else { if (com->do_timestamp) microtime(&com->timestamp); if (ioptr <= com->ihighwater && ioptr + count > com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, com->mcr_rts_reg, cy_align, com->mcr_image &= ~com->mcr_rts); #endif com_events += count; do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) swi_sched(sio_fast_ih, 0); #endif ioptr[0] = recv_data; ioptr[com->ierroff] = 0; ++ioptr; } while (--count != 0); } com->iptr = ioptr; } cont: /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_RIR, cy_align, save_rir & ~(CD1400_RIR_RDIREQ | CD1400_RIR_RBUSY)); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } if (status & CD1400_SVRR_MDMCH) { struct com_s *com; u_char modem_status; #ifdef PollMode u_char save_mir; #else u_char vector; #endif #ifdef PollMode save_mir = cd_inb(iobase, CD1400_MIR, cy_align); /* enter modem service */ cd_outb(iobase, CD1400_CAR, cy_align, save_mir); com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS)->car = save_mir & CD1400_CAR_CHAN; com = com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS + (save_mir & CD1400_MIR_CHAN)); #else /* ack modem service */ vector = cy_inb(iobase, CY8_SVCACKM, cy_align); com = com_addr(baseu + ((vector >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif ++com->mdm; modem_status = cd_inb(iobase, CD1400_MSVR2, cy_align); if (modem_status != com->last_modem_status) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * Schedule high level to handle DCD changes. Note * that we don't use the delta bits anywhere. Some * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; swi_sched(sio_fast_ih, 0); } #ifdef SOFT_CTS_OFLOW /* handle CTS change immediately for crisp flow ctl */ if (com->state & CS_CTS_OFLOW) { if (modem_status & MSR_CTS) { com->state |= CS_ODEVREADY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY) && !(com->intr_enable & CD1400_SRER_TXRDY)) cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable = com->intr_enable & ~CD1400_SRER_TXMPTY | CD1400_SRER_TXRDY); } else { com->state &= ~CS_ODEVREADY; if (com->intr_enable & CD1400_SRER_TXRDY) cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable = com->intr_enable & ~CD1400_SRER_TXRDY | CD1400_SRER_TXMPTY); } } #endif } /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_MIR, cy_align, save_mir & ~(CD1400_MIR_RDIREQ | CD1400_MIR_RBUSY)); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } if (status & CD1400_SVRR_TXRDY) { struct com_s *com; #ifdef PollMode u_char save_tir; #else u_char vector; #endif #ifdef PollMode save_tir = cd_inb(iobase, CD1400_TIR, cy_align); /* enter tx service */ cd_outb(iobase, CD1400_CAR, cy_align, save_tir); com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS)->car = save_tir & CD1400_CAR_CHAN; com = com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS + (save_tir & CD1400_TIR_CHAN)); #else /* ack transmit service */ vector = cy_inb(iobase, CY8_SVCACKT, cy_align); com = com_addr(baseu + ((vector >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif if (com->etc != ETC_NONE) { if (com->intr_enable & CD1400_SRER_TXRDY) { /* * Here due to sloppy SRER_TXRDY * enabling. Ignore. Come back when * tx is empty. */ cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable = (com->intr_enable & ~CD1400_SRER_TXRDY) | CD1400_SRER_TXMPTY); goto terminate_tx_service; } switch (com->etc) { case CD1400_ETC_SENDBREAK: case CD1400_ETC_STOPBREAK: /* * Start the command. Come back on * next tx empty interrupt, hopefully * after command has been executed. */ cd_outb(iobase, CD1400_COR2, cy_align, com->cor[1] |= CD1400_COR2_ETC); cd_outb(iobase, CD1400_TDR, cy_align, CD1400_ETC_CMD); cd_outb(iobase, CD1400_TDR, cy_align, com->etc); if (com->etc == CD1400_ETC_SENDBREAK) com->etc = ETC_BREAK_STARTING; else com->etc = ETC_BREAK_ENDING; goto terminate_tx_service; case ETC_BREAK_STARTING: /* * BREAK is now on. Continue with * SRER_TXMPTY processing, hopefully * don't come back. */ com->etc = ETC_BREAK_STARTED; break; case ETC_BREAK_STARTED: /* * Came back due to sloppy SRER_TXMPTY * enabling. Hope again. */ break; case ETC_BREAK_ENDING: /* * BREAK is now off. Continue with * SRER_TXMPTY processing and don't * come back. The SWI handler will * restart tx interrupts if necessary. */ cd_outb(iobase, CD1400_COR2, cy_align, com->cor[1] &= ~CD1400_COR2_ETC); com->etc = ETC_BREAK_ENDED; if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; swi_sched(sio_fast_ih, 0); } break; case ETC_BREAK_ENDED: /* * Shouldn't get here. Hope again. */ break; } } if (com->intr_enable & CD1400_SRER_TXMPTY) { if (!(com->extra_state & CSE_ODONE)) { com_events += LOTS_OF_EVENTS; com->extra_state |= CSE_ODONE; swi_sched(sio_fast_ih, 0); } cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable &= ~CD1400_SRER_TXMPTY); goto terminate_tx_service; } if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { u_char *ioptr; u_int ocount; ioptr = com->obufq.l_head; ocount = com->obufq.l_tail - ioptr; if (ocount > CD1400_TX_FIFO_SIZE) ocount = CD1400_TX_FIFO_SIZE; com->bytes_out += ocount; do cd_outb(iobase, CD1400_TDR, cy_align, *ioptr++); while (--ocount != 0); com->obufq.l_head = ioptr; if (ioptr >= com->obufq.l_tail) { struct lbq *qp; qp = com->obufq.l_next; qp->l_queued = FALSE; qp = qp->l_next; if (qp != NULL) { com->obufq.l_head = qp->l_head; com->obufq.l_tail = qp->l_tail; com->obufq.l_next = qp; } else { /* output just completed */ com->state &= ~CS_BUSY; /* * The setting of CSE_ODONE may be * stale here. We currently only * use it when CS_BUSY is set, and * fixing it when we clear CS_BUSY * is easiest. */ if (com->extra_state & CSE_ODONE) { com_events -= LOTS_OF_EVENTS; com->extra_state &= ~CSE_ODONE; } cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable = (com->intr_enable & ~CD1400_SRER_TXRDY) | CD1400_SRER_TXMPTY); } if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; /* handle at high level ASAP */ swi_sched(sio_fast_ih, 0); } } } /* terminate service context */ terminate_tx_service: #ifdef PollMode cd_outb(iobase, CD1400_TIR, cy_align, save_tir & ~(CD1400_TIR_RDIREQ | CD1400_TIR_RBUSY)); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } } /* ensure an edge for the next interrupt */ cy_outb(cy_iobase, CY_CLEAR_INTR, cy_align, 0); swi_sched(sio_slow_ih, SWI_DELAY); COM_UNLOCK(); } static int sioioctl(dev, cmd, data, flag, td) dev_t dev; u_long cmd; caddr_t data; int flag; struct thread *td; { struct com_s *com; int error; int mynor; int s; struct tty *tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) int oldcmd; struct termios term; #endif mynor = minor(dev); com = com_addr(MINOR_TO_UNIT(mynor)); if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->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); default: return (ENOTTY); } } tp = com->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 *)data; struct termios *lt = mynor & CALLOUT_MASK ? &com->lt_out : &com->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); 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 = ttyioctl(dev, cmd, data, flag, td); disc_optim(tp, &tp->t_termios, com); if (error != ENOTTY) return (error); s = spltty(); switch (cmd) { case TIOCSBRK: #if 0 outb(iobase + com_cfcr, com->cfcr_image |= CFCR_SBREAK); #else cd_etc(com, CD1400_ETC_SENDBREAK); #endif break; case TIOCCBRK: #if 0 outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); #else cd_etc(com, CD1400_ETC_STOPBREAK); #endif break; case TIOCSDTR: (void)commctl(com, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void)commctl(com, TIOCM_DTR, DMBIC); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: (void)commctl(com, *(int *)data, DMSET); break; case TIOCMBIS: (void)commctl(com, *(int *)data, DMBIS); break; case TIOCMBIC: (void)commctl(com, *(int *)data, DMBIC); break; case TIOCMGET: *(int *)data = commctl(com, 0, DMGET); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(td); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); return (ENOTTY); } splx(s); return (0); } static void siopoll(void *arg) { int unit; #ifdef CyDebug ++cy_timeouts; #endif if (com_events == 0) return; repeat: for (unit = 0; unit < NSIO; ++unit) { struct com_s *com; int incc; struct tty *tp; com = com_addr(unit); if (com == NULL) continue; tp = com->tp; if (tp == NULL) { /* * XXX forget any events related to closed devices * (actually never opened devices) so that we don't * loop. */ critical_enter(); COM_LOCK(); incc = com->iptr - com->ibuf; com->iptr = com->ibuf; if (com->state & CS_CHECKMSR) { incc += LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; } com_events -= incc; COM_UNLOCK(); critical_exit(); if (incc != 0) log(LOG_DEBUG, "sio%d: %d events for device with no tp\n", unit, incc); continue; } if (com->iptr != com->ibuf) { critical_enter(); COM_LOCK(); sioinput(com); COM_UNLOCK(); critical_exit(); } if (com->state & CS_CHECKMSR) { u_char delta_modem_status; critical_enter(); COM_LOCK(); sioinput(com); delta_modem_status = com->last_modem_status ^ com->prev_modem_status; com->prev_modem_status = com->last_modem_status; com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; COM_UNLOCK(); critical_exit(); if (delta_modem_status & MSR_DCD) ttyld_modem(tp, com->prev_modem_status & MSR_DCD); } if (com->extra_state & CSE_ODONE) { critical_enter(); COM_LOCK(); com_events -= LOTS_OF_EVENTS; com->extra_state &= ~CSE_ODONE; COM_UNLOCK(); critical_exit(); if (!(com->state & CS_BUSY)) { tp->t_state &= ~TS_BUSY; ttwwakeup(com->tp); } if (com->etc != ETC_NONE) { if (com->etc == ETC_BREAK_ENDED) com->etc = ETC_NONE; wakeup(&com->etc); } } if (com->state & CS_ODONE) { critical_enter(); COM_LOCK(); com_events -= LOTS_OF_EVENTS; com->state &= ~CS_ODONE; COM_UNLOCK(); critical_exit(); ttyld_start(tp); } if (com_events == 0) break; } if (com_events >= LOTS_OF_EVENTS) goto repeat; } static int comparam(tp, t) struct tty *tp; struct termios *t; { int bits; int cflag; struct com_s *com; u_char cor_change; u_long cy_clock; int idivisor; int iflag; int iprescaler; int itimeout; int odivisor; int oprescaler; u_char opt; int s; int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); /* check requested parameters */ cy_clock = CY_CLOCK(com->gfrcr_image); idivisor = comspeed(t->c_ispeed, cy_clock, &iprescaler); if (idivisor <= 0) return (EINVAL); odivisor = comspeed(t->c_ospeed != 0 ? t->c_ospeed : tp->t_ospeed, cy_clock, &oprescaler); if (odivisor <= 0) return (EINVAL); /* parameters are OK, convert them to the com struct and the device */ s = spltty(); if (t->c_ospeed == 0) (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ else (void)commctl(com, TIOCM_DTR, DMBIS); (void) siosetwater(com, t->c_ispeed); /* XXX we don't actually change the speed atomically. */ cd_setreg(com, CD1400_RBPR, idivisor); cd_setreg(com, CD1400_RCOR, iprescaler); cd_setreg(com, CD1400_TBPR, odivisor); cd_setreg(com, CD1400_TCOR, oprescaler); /* * channel control * receiver enable * transmitter enable (always set) */ cflag = t->c_cflag; opt = CD1400_CCR_CMDCHANCTL | CD1400_CCR_XMTEN | (cflag & CREAD ? CD1400_CCR_RCVEN : CD1400_CCR_RCVDIS); if (opt != com->channel_control) { com->channel_control = opt; cd1400_channel_cmd(com, opt); } #ifdef Smarts /* set special chars */ /* XXX if one is _POSIX_VDISABLE, can't use some others */ if (t->c_cc[VSTOP] != _POSIX_VDISABLE) cd_setreg(com, CD1400_SCHR1, t->c_cc[VSTOP]); if (t->c_cc[VSTART] != _POSIX_VDISABLE) cd_setreg(com, CD1400_SCHR2, t->c_cc[VSTART]); if (t->c_cc[VINTR] != _POSIX_VDISABLE) cd_setreg(com, CD1400_SCHR3, t->c_cc[VINTR]); if (t->c_cc[VSUSP] != _POSIX_VDISABLE) cd_setreg(com, CD1400_SCHR4, t->c_cc[VSUSP]); #endif /* * set channel option register 1 - * parity mode * stop bits * char length */ opt = 0; /* parity */ if (cflag & PARENB) { if (cflag & PARODD) opt |= CD1400_COR1_PARODD; opt |= CD1400_COR1_PARNORMAL; } iflag = t->c_iflag; if (!(iflag & INPCK)) opt |= CD1400_COR1_NOINPCK; bits = 1 + 1; /* stop bits */ if (cflag & CSTOPB) { ++bits; opt |= CD1400_COR1_STOP2; } /* char length */ switch (cflag & CSIZE) { case CS5: bits += 5; opt |= CD1400_COR1_CS5; break; case CS6: bits += 6; opt |= CD1400_COR1_CS6; break; case CS7: bits += 7; opt |= CD1400_COR1_CS7; break; default: bits += 8; opt |= CD1400_COR1_CS8; break; } cor_change = 0; if (opt != com->cor[0]) { cor_change |= CD1400_CCR_COR1; cd_setreg(com, CD1400_COR1, com->cor[0] = opt); } /* * Set receive time-out period, normally to max(one char time, 5 ms). */ itimeout = (1000 * bits + t->c_ispeed - 1) / t->c_ispeed; #ifdef SOFT_HOTCHAR #define MIN_RTP 1 #else #define MIN_RTP 5 #endif if (itimeout < MIN_RTP) itimeout = MIN_RTP; if (!(t->c_lflag & ICANON) && t->c_cc[VMIN] != 0 && t->c_cc[VTIME] != 0 && t->c_cc[VTIME] * 10 > itimeout) itimeout = t->c_cc[VTIME] * 10; if (itimeout > 255) itimeout = 255; cd_setreg(com, CD1400_RTPR, itimeout); /* * set channel option register 2 - * flow control */ opt = 0; #ifdef Smarts if (iflag & IXANY) opt |= CD1400_COR2_IXANY; if (iflag & IXOFF) opt |= CD1400_COR2_IXOFF; #endif #ifndef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_COR2_CCTS_OFLOW; #endif critical_enter(); COM_LOCK(); if (opt != com->cor[1]) { cor_change |= CD1400_CCR_COR2; cd_setreg(com, CD1400_COR2, com->cor[1] = opt); } COM_UNLOCK(); critical_exit(); /* * set channel option register 3 - * receiver FIFO interrupt threshold * flow control */ opt = RxFifoThreshold; #ifdef Smarts if (t->c_lflag & ICANON) opt |= CD1400_COR3_SCD34; /* detect INTR & SUSP chars */ if (iflag & IXOFF) /* detect and transparently handle START and STOP chars */ opt |= CD1400_COR3_FCT | CD1400_COR3_SCD12; #endif if (opt != com->cor[2]) { cor_change |= CD1400_CCR_COR3; cd_setreg(com, CD1400_COR3, com->cor[2] = opt); } /* notify the CD1400 if COR1-3 have changed */ if (cor_change) cd1400_channel_cmd(com, CD1400_CCR_CMDCORCHG | cor_change); /* * set channel option register 4 - * CR/NL processing * break processing * received exception processing */ opt = 0; if (iflag & IGNCR) opt |= CD1400_COR4_IGNCR; #ifdef Smarts /* * we need a new ttyinput() for this, as we don't want to * have ICRNL && INLCR being done in both layers, or to have * synchronisation problems */ if (iflag & ICRNL) opt |= CD1400_COR4_ICRNL; if (iflag & INLCR) opt |= CD1400_COR4_INLCR; #endif if (iflag & IGNBRK) opt |= CD1400_COR4_IGNBRK | CD1400_COR4_NOBRKINT; /* * The `-ignbrk -brkint parmrk' case is not handled by the hardware, * so only tell the hardware about -brkint if -parmrk. */ if (!(iflag & (BRKINT | PARMRK))) opt |= CD1400_COR4_NOBRKINT; #if 0 /* XXX using this "intelligence" breaks reporting of overruns. */ if (iflag & IGNPAR) opt |= CD1400_COR4_PFO_DISCARD; else { if (iflag & PARMRK) opt |= CD1400_COR4_PFO_ESC; else opt |= CD1400_COR4_PFO_NUL; } #else opt |= CD1400_COR4_PFO_EXCEPTION; #endif cd_setreg(com, CD1400_COR4, opt); /* * set channel option register 5 - */ opt = 0; if (iflag & ISTRIP) opt |= CD1400_COR5_ISTRIP; if (t->c_iflag & IEXTEN) /* enable LNEXT (e.g. ctrl-v quoting) handling */ opt |= CD1400_COR5_LNEXT; #ifdef Smarts if (t->c_oflag & ONLCR) opt |= CD1400_COR5_ONLCR; if (t->c_oflag & OCRNL) opt |= CD1400_COR5_OCRNL; #endif cd_setreg(com, CD1400_COR5, opt); /* * We always generate modem status change interrupts for CD changes. * Among other things, this is necessary to track TS_CARR_ON for * pstat to print even when the driver doesn't care. CD changes * should be rare so interrupts for them are not worth extra code to * avoid. We avoid interrupts for other modem status changes (except * for CTS changes when SOFT_CTS_OFLOW is configured) since this is * simplest and best. */ /* * set modem change option register 1 * generate modem interrupts on which 1 -> 0 input transitions * also controls auto-DTR output flow-control, which we don't use */ opt = CD1400_MCOR1_CDzd; #ifdef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_MCOR1_CTSzd; #endif cd_setreg(com, CD1400_MCOR1, opt); /* * set modem change option register 2 * generate modem interrupts on specific 0 -> 1 input transitions */ opt = CD1400_MCOR2_CDod; #ifdef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_MCOR2_CTSod; #endif cd_setreg(com, CD1400_MCOR2, opt); /* * XXX should have done this long ago, but there is too much state * to change all atomically. */ critical_enter(); COM_LOCK(); com->state &= ~CS_TTGO; if (!(tp->t_state & TS_TTSTOP)) com->state |= CS_TTGO; if (cflag & CRTS_IFLOW) { com->state |= CS_RTS_IFLOW; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else cd_setreg(com, com->mcr_rts_reg, com->mcr_image |= com->mcr_rts); #endif } /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state |= CS_ODEVREADY; #ifdef SOFT_CTS_OFLOW com->state &= ~CS_CTS_OFLOW; if (cflag & CCTS_OFLOW) { com->state |= CS_CTS_OFLOW; if (!(com->last_modem_status & MSR_CTS)) com->state &= ~CS_ODEVREADY; } #endif /* XXX shouldn't call functions while intrs are disabled. */ disc_optim(tp, t, com); #if 0 /* * Recover from fiddling with CS_TTGO. We used to call siointr1() * unconditionally, but that defeated the careful discarding of * stale input in sioopen(). */ if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); #endif if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { if (!(com->intr_enable & CD1400_SRER_TXRDY)) cd_setreg(com, CD1400_SRER, com->intr_enable = (com->intr_enable & ~CD1400_SRER_TXMPTY) | CD1400_SRER_TXRDY); } else { if (com->intr_enable & CD1400_SRER_TXRDY) cd_setreg(com, CD1400_SRER, com->intr_enable = (com->intr_enable & ~CD1400_SRER_TXRDY) | CD1400_SRER_TXMPTY); } COM_UNLOCK(); critical_exit(); splx(s); comstart(tp); if (com->ibufold != NULL) { free(com->ibufold, M_DEVBUF); com->ibufold = NULL; } return (0); } static int siosetwater(com, speed) struct com_s *com; speed_t speed; { int cp4ticks; u_char *ibuf; int ibufsize; struct tty *tp; /* * Make the buffer size large enough to handle a softtty interrupt * latency of about 2 ticks without loss of throughput or data * (about 3 ticks if input flow control is not used or not honoured, * but a bit less for CS5-CS7 modes). */ cp4ticks = speed / 10 / hz * 4; for (ibufsize = 128; ibufsize < cp4ticks;) ibufsize <<= 1; if (ibufsize == com->ibufsize) { return (0); } /* * Allocate input buffer. The extra factor of 2 in the size is * to allow for an error byte for each input byte. */ ibuf = malloc(2 * ibufsize, M_DEVBUF, M_NOWAIT); if (ibuf == NULL) { return (ENOMEM); } /* Initialize non-critical variables. */ com->ibufold = com->ibuf; com->ibufsize = ibufsize; tp = com->tp; if (tp != NULL) { tp->t_ififosize = 2 * ibufsize; tp->t_ispeedwat = (speed_t)-1; tp->t_ospeedwat = (speed_t)-1; } /* * Read current input buffer, if any. Continue with interrupts * disabled. */ critical_enter(); COM_LOCK(); if (com->iptr != com->ibuf) sioinput(com); /*- * Initialize critical variables, including input buffer watermarks. * The external device is asked to stop sending when the buffer * exactly reaches high water, or when the high level requests it. * The high level is notified immediately (rather than at a later * clock tick) when this watermark is reached. * The buffer size is chosen so the watermark should almost never * be reached. * The low watermark is invisibly 0 since the buffer is always * emptied all at once. */ com->iptr = com->ibuf = ibuf; com->ibufend = ibuf + ibufsize; com->ierroff = ibufsize; com->ihighwater = ibuf + 3 * ibufsize / 4; COM_UNLOCK(); critical_exit(); return (0); } static void comstart(tp) struct tty *tp; { struct com_s *com; int s; #ifdef CyDebug bool_t started; #endif int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); s = spltty(); #ifdef CyDebug ++com->start_count; started = FALSE; #endif critical_enter(); COM_LOCK(); if (tp->t_state & TS_TTSTOP) { com->state &= ~CS_TTGO; if (com->intr_enable & CD1400_SRER_TXRDY) cd_setreg(com, CD1400_SRER, com->intr_enable = (com->intr_enable & ~CD1400_SRER_TXRDY) | CD1400_SRER_TXMPTY); } else { com->state |= CS_TTGO; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY) && !(com->intr_enable & CD1400_SRER_TXRDY)) cd_setreg(com, CD1400_SRER, com->intr_enable = (com->intr_enable & ~CD1400_SRER_TXMPTY) | CD1400_SRER_TXRDY); } if (tp->t_state & TS_TBLOCK) { if (com->mcr_image & com->mcr_rts && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_setreg(com, com->mcr_rts_reg, com->mcr_image &= ~com->mcr_rts); #endif } else { if (!(com->mcr_image & com->mcr_rts) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else cd_setreg(com, com->mcr_rts_reg, com->mcr_image |= com->mcr_rts); #endif } COM_UNLOCK(); critical_exit(); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(s); return; } if (tp->t_outq.c_cc != 0) { struct lbq *qp; struct lbq *next; if (!com->obufs[0].l_queued) { #ifdef CyDebug started = TRUE; #endif com->obufs[0].l_tail = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, sizeof com->obuf1); com->obufs[0].l_next = NULL; com->obufs[0].l_queued = TRUE; critical_enter(); COM_LOCK(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[0]; } else { com->obufq.l_head = com->obufs[0].l_head; com->obufq.l_tail = com->obufs[0].l_tail; com->obufq.l_next = &com->obufs[0]; com->state |= CS_BUSY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) cd_setreg(com, CD1400_SRER, com->intr_enable = (com->intr_enable & ~CD1400_SRER_TXMPTY) | CD1400_SRER_TXRDY); } COM_UNLOCK(); critical_exit(); } if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { #ifdef CyDebug started = TRUE; #endif com->obufs[1].l_tail = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, sizeof com->obuf2); com->obufs[1].l_next = NULL; com->obufs[1].l_queued = TRUE; critical_enter(); COM_LOCK(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[1]; } else { com->obufq.l_head = com->obufs[1].l_head; com->obufq.l_tail = com->obufs[1].l_tail; com->obufq.l_next = &com->obufs[1]; com->state |= CS_BUSY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) cd_setreg(com, CD1400_SRER, com->intr_enable = (com->intr_enable & ~CD1400_SRER_TXMPTY) | CD1400_SRER_TXRDY); } COM_UNLOCK(); critical_exit(); } tp->t_state |= TS_BUSY; } #ifdef CyDebug if (started) ++com->start_real; #endif #if 0 critical_enter(); COM_LOCK(); if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); /* fake interrupt to start output */ COM_UNLOCK(); critical_exit(); #endif ttwwakeup(tp); splx(s); } static void comstop(tp, rw) struct tty *tp; int rw; { struct com_s *com; bool_t wakeup_etc; com = com_addr(DEV_TO_UNIT(tp->t_dev)); wakeup_etc = FALSE; critical_enter(); COM_LOCK(); if (rw & FWRITE) { com->obufs[0].l_queued = FALSE; com->obufs[1].l_queued = FALSE; if (com->extra_state & CSE_ODONE) { com_events -= LOTS_OF_EVENTS; com->extra_state &= ~CSE_ODONE; if (com->etc != ETC_NONE) { if (com->etc == ETC_BREAK_ENDED) com->etc = ETC_NONE; wakeup_etc = TRUE; } } com->tp->t_state &= ~TS_BUSY; if (com->state & CS_ODONE) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); } if (rw & FREAD) { /* XXX no way to reset only input fifo. */ com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } COM_UNLOCK(); critical_exit(); if (wakeup_etc) wakeup(&com->etc); if (rw & FWRITE && com->etc == ETC_NONE) cd1400_channel_cmd(com, CD1400_CCR_CMDRESET | CD1400_CCR_FTF); comstart(tp); } static int commctl(com, bits, how) struct com_s *com; int bits; int how; { int mcr; int msr; if (how == DMGET) { if (com->channel_control & CD1400_CCR_RCVEN) bits |= TIOCM_LE; mcr = com->mcr_image; if (mcr & com->mcr_dtr) bits |= TIOCM_DTR; if (mcr & com->mcr_rts) /* XXX wired on for Cyclom-8Ys */ bits |= TIOCM_RTS; /* * We must read the modem status from the hardware because * we don't generate modem status change interrupts for all * changes, so com->prev_modem_status is not guaranteed to * be up to date. This is safe, unlike for sio, because * reading the status register doesn't clear pending modem * status change interrupts. */ msr = cd_getreg(com, CD1400_MSVR2); if (msr & MSR_CTS) bits |= TIOCM_CTS; if (msr & MSR_DCD) bits |= TIOCM_CD; if (msr & MSR_DSR) bits |= TIOCM_DSR; if (msr & MSR_RI) /* XXX not connected except for Cyclom-16Y? */ bits |= TIOCM_RI; return (bits); } mcr = 0; if (bits & TIOCM_DTR) mcr |= com->mcr_dtr; if (bits & TIOCM_RTS) mcr |= com->mcr_rts; critical_enter(); COM_LOCK(); switch (how) { case DMSET: com->mcr_image = mcr; cd_setreg(com, CD1400_MSVR1, mcr); cd_setreg(com, CD1400_MSVR2, mcr); break; case DMBIS: com->mcr_image = mcr = com->mcr_image | mcr; cd_setreg(com, CD1400_MSVR1, mcr); cd_setreg(com, CD1400_MSVR2, mcr); break; case DMBIC: com->mcr_image = mcr = com->mcr_image & ~mcr; cd_setreg(com, CD1400_MSVR1, mcr); cd_setreg(com, CD1400_MSVR2, mcr); break; } COM_UNLOCK(); critical_exit(); return (0); } static void siosettimeout() { struct com_s *com; bool_t someopen; int unit; /* * Set our timeout period to 1 second if no polled devices are open. * Otherwise set it to max(1/200, 1/hz). * Enable timeouts iff some device is open. */ untimeout(comwakeup, (void *)NULL, sio_timeout_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < NSIO; ++unit) { com = com_addr(unit); if (com != NULL && com->tp != NULL && com->tp->t_state & TS_ISOPEN) { someopen = TRUE; #if 0 if (com->poll || com->poll_output) { sio_timeout = hz > 200 ? hz / 200 : 1; break; } #endif } } if (someopen) { sio_timeouts_until_log = hz / sio_timeout; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); #if 0 /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < NSIO; ++unit) { com = com_addr(unit); if (com != NULL && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { critical_enter(); COM_LOCK(); siointr1(com); COM_UNLOCK(); critical_exit(); } } #endif /* * Check for and log errors, but not too often. */ if (--sio_timeouts_until_log > 0) return; sio_timeouts_until_log = hz / sio_timeout; for (unit = 0; unit < NSIO; ++unit) { int errnum; com = com_addr(unit); if (com == NULL) continue; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { u_int delta; u_long total; critical_enter(); COM_LOCK(); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; COM_UNLOCK(); critical_exit(); if (delta == 0) continue; total = com->error_counts[errnum] += delta; log(LOG_ERR, "cy%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); } } } static void disc_optim(tp, t, com) struct tty *tp; struct termios *t; struct com_s *com; { #ifndef SOFT_HOTCHAR u_char opt; #endif - /* - * XXX can skip a lot more cases if Smarts. Maybe - * (IGNCR | ISTRIP | IXON) in c_iflag. But perhaps we - * shouldn't skip if (TS_CNTTB | TS_LNCH) is set in t_state. - */ - if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) - && (!(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; - com->hotchar = linesw[tp->t_line].l_hotchar; + com->hotchar = ttyldoptim(tp); #ifndef SOFT_HOTCHAR opt = com->cor[2] & ~CD1400_COR3_SCD34; if (com->hotchar != 0) { cd_setreg(com, CD1400_SCHR3, com->hotchar); cd_setreg(com, CD1400_SCHR4, com->hotchar); opt |= CD1400_COR3_SCD34; } if (opt != com->cor[2]) { cd_setreg(com, CD1400_COR3, com->cor[2] = opt); cd1400_channel_cmd(com, CD1400_CCR_CMDCORCHG | CD1400_CCR_COR3); } #endif } #ifdef Smarts /* standard line discipline input routine */ int cyinput(c, tp) int c; struct tty *tp; { /* XXX duplicate ttyinput(), but without the IXOFF/IXON/ISTRIP/IPARMRK * bits, as they are done by the CD1400. Hardly worth the effort, * given that high-throughput sessions are raw anyhow. */ } #endif /* Smarts */ static int comspeed(speed, cy_clock, prescaler_io) speed_t speed; u_long cy_clock; int *prescaler_io; { int actual; int error; int divider; int prescaler; int prescaler_unit; if (speed == 0) return (0); if (speed < 0 || speed > 150000) return (-1); /* determine which prescaler to use */ for (prescaler_unit = 4, prescaler = 2048; prescaler_unit; prescaler_unit--, prescaler >>= 2) { if (cy_clock / prescaler / speed > 63) break; } divider = (cy_clock / prescaler * 2 / speed + 1) / 2; /* round off */ if (divider > 255) divider = 255; actual = cy_clock/prescaler/divider; /* 10 times error in percent: */ error = ((actual - (long)speed) * 2000 / (long)speed + 1) / 2; /* 3.0% max error tolerance */ if (error < -30 || error > 30) return (-1); #if 0 printf("prescaler = %d (%d)\n", prescaler, prescaler_unit); printf("divider = %d (%x)\n", divider, divider); printf("actual = %d\n", actual); printf("error = %d\n", error); #endif *prescaler_io = prescaler_unit; return (divider); } static void cd1400_channel_cmd(com, cmd) struct com_s *com; int cmd; { cd1400_channel_cmd_wait(com); cd_setreg(com, CD1400_CCR, cmd); cd1400_channel_cmd_wait(com); } static void cd1400_channel_cmd_wait(com) struct com_s *com; { struct timeval start; struct timeval tv; long usec; if (cd_getreg(com, CD1400_CCR) == 0) return; microtime(&start); for (;;) { if (cd_getreg(com, CD1400_CCR) == 0) return; microtime(&tv); usec = 1000000 * (tv.tv_sec - start.tv_sec) + tv.tv_usec - start.tv_usec; if (usec >= 5000) { log(LOG_ERR, "cy%d: channel command timeout (%ld usec)\n", com->unit, usec); return; } } } static void cd_etc(com, etc) struct com_s *com; int etc; { /* * We can't change the hardware's ETC state while there are any * characters in the tx fifo, since those characters would be * interpreted as commands! Unputting characters from the fifo * is difficult, so we wait up to 12 character times for the fifo * to drain. The command will be delayed for up to 2 character * times for the tx to become empty. Unputting characters from * the tx holding and shift registers is impossible, so we wait * for the tx to become empty so that the command is sure to be * executed soon after we issue it. */ critical_enter(); COM_LOCK(); if (com->etc == etc) goto wait; if ((etc == CD1400_ETC_SENDBREAK && (com->etc == ETC_BREAK_STARTING || com->etc == ETC_BREAK_STARTED)) || (etc == CD1400_ETC_STOPBREAK && (com->etc == ETC_BREAK_ENDING || com->etc == ETC_BREAK_ENDED || com->etc == ETC_NONE))) { COM_UNLOCK(); critical_exit(); return; } com->etc = etc; cd_setreg(com, CD1400_SRER, com->intr_enable = (com->intr_enable & ~CD1400_SRER_TXRDY) | CD1400_SRER_TXMPTY); wait: COM_UNLOCK(); critical_exit(); while (com->etc == etc && tsleep(&com->etc, TTIPRI | PCATCH, "cyetc", 0) == 0) continue; } static int cd_getreg(com, reg) struct com_s *com; int reg; { struct com_s *basecom; u_char car; int cy_align; cy_addr iobase; #ifdef SMP int need_unlock; #endif int val; basecom = com_addr(com->unit & ~(CD1400_NO_OF_CHANNELS - 1)); car = com->unit & CD1400_CAR_CHAN; cy_align = com->cy_align; iobase = com->iobase; critical_enter(); #ifdef SMP need_unlock = 0; if (!mtx_owned(&sio_lock)) { COM_LOCK(); need_unlock = 1; } #endif if (basecom->car != car) cd_outb(iobase, CD1400_CAR, cy_align, basecom->car = car); val = cd_inb(iobase, reg, cy_align); #ifdef SMP if (need_unlock) COM_UNLOCK(); #endif critical_exit(); return (val); } static void cd_setreg(com, reg, val) struct com_s *com; int reg; int val; { struct com_s *basecom; u_char car; int cy_align; cy_addr iobase; #ifdef SMP int need_unlock; #endif basecom = com_addr(com->unit & ~(CD1400_NO_OF_CHANNELS - 1)); car = com->unit & CD1400_CAR_CHAN; cy_align = com->cy_align; iobase = com->iobase; critical_enter(); #ifdef SMP need_unlock = 0; if (!mtx_owned(&sio_lock)) { COM_LOCK(); need_unlock = 1; } #endif if (basecom->car != car) cd_outb(iobase, CD1400_CAR, cy_align, basecom->car = car); cd_outb(iobase, reg, cy_align, val); #ifdef SMP if (need_unlock) COM_UNLOCK(); #endif critical_exit(); } #ifdef CyDebug /* useful in ddb */ void cystatus(unit) int unit; { struct com_s *com; cy_addr iobase; u_int ocount; struct tty *tp; com = com_addr(unit); printf("info for channel %d\n", unit); printf("------------------\n"); printf("total cyclom service probes:\t%d\n", cy_svrr_probes); printf("calls to upper layer:\t\t%d\n", cy_timeouts); if (com == NULL) return; iobase = com->iobase; printf("\n"); printf("cd1400 base address:\\tt%p\n", iobase); printf("saved channel_control:\t\t0x%02x\n", com->channel_control); printf("saved cor1-3:\t\t\t0x%02x 0x%02x 0x%02x\n", com->cor[0], com->cor[1], com->cor[2]); printf("service request enable reg:\t0x%02x (0x%02x cached)\n", cd_getreg(com, CD1400_SRER), com->intr_enable); printf("service request register:\t0x%02x\n", cd_inb(iobase, CD1400_SVRR, com->cy_align)); printf("modem status:\t\t\t0x%02x (0x%02x cached)\n", cd_getreg(com, CD1400_MSVR2), com->prev_modem_status); printf("rx/tx/mdm interrupt registers:\t0x%02x 0x%02x 0x%02x\n", cd_inb(iobase, CD1400_RIR, com->cy_align), cd_inb(iobase, CD1400_TIR, com->cy_align), cd_inb(iobase, CD1400_MIR, com->cy_align)); printf("\n"); printf("com state:\t\t\t0x%02x\n", com->state); printf("calls to comstart():\t\t%d (%d useful)\n", com->start_count, com->start_real); printf("rx buffer chars free:\t\t%d\n", com->iptr - com->ibuf); ocount = 0; if (com->obufs[0].l_queued) ocount += com->obufs[0].l_tail - com->obufs[0].l_head; if (com->obufs[1].l_queued) ocount += com->obufs[1].l_tail - com->obufs[1].l_head; printf("tx buffer chars:\t\t%u\n", ocount); printf("received chars:\t\t\t%d\n", com->bytes_in); printf("received exceptions:\t\t%d\n", com->recv_exception); printf("modem signal deltas:\t\t%d\n", com->mdm); printf("transmitted chars:\t\t%d\n", com->bytes_out); printf("\n"); tp = com->tp; if (tp != NULL) { printf("tty state:\t\t\t0x%08x\n", tp->t_state); printf( "upper layer queue lengths:\t%d raw, %d canon, %d output\n", tp->t_rawq.c_cc, tp->t_canq.c_cc, tp->t_outq.c_cc); } else printf("tty state:\t\t\tclosed\n"); } #endif /* CyDebug */ diff --git a/sys/dev/digi/digi.c b/sys/dev/digi/digi.c index d13e50f6958d..e99e737c2554 100644 --- a/sys/dev/digi/digi.c +++ b/sys/dev/digi/digi.c @@ -1,1944 +1,1930 @@ /*- * Copyright (c) 2001 Brian Somers * based on work by Slawa Olhovchenkov * John Prince * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 = { .d_version = D_VERSION, .d_open = digiopen, .d_close = digiclose, .d_read = digiread, .d_write = digiwrite, .d_ioctl = digiioctl, .d_name = driver_name, .d_flags = D_TTY | D_NEEDGIANT, }; 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 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) ttyld_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 = ttyld_open(tp, dev); DLOG(DIGIDB_OPEN, (sc->dev, "port %d: l_open error = %d\n", pnum, error)); - digi_disc_optim(tp, &tp->t_termios, port); + ttyldoptim(tp); 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(); ttyld_close(tp, flag); - digi_disc_optim(tp, &tp->t_termios, port); + ttyldoptim(tp); 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 = ttyld_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 = ttyld_write(tp, uio, flag); DLOG(DIGIDB_WRITE, (sc->dev, "port %d: write() returns %d\n", pnum, error)); return (error); } /* * Load module "digi_.ko" and look for a symbol called digi_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 = ttyioctl(dev, cmd, data, flag, td); if (error == 0 && cmd == TIOCGETA) ((struct termios *)data)->c_iflag |= port->c_iflag; - digi_disc_optim(tp, &tp->t_termios, port); + ttyldoptim(tp); if (error >= 0 && error != ENOTTY) return (error); s = spltty(); 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;) { ttyld_rint(tp, port->rxbuf[tail]); 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); ttyld_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) { ttyld_rint(tp, 'R'); ttyld_rint(tp, 'I'); ttyld_rint(tp, 'N'); ttyld_rint(tp, 'G'); ttyld_rint(tp, '\r'); ttyld_rint(tp, '\n'); } } } if (event.event & BREAK_IND) { DLOG(DIGIDB_MODEM, (sc->dev, "port %d: BREAK_IND\n", event.pnum)); ttyld_rint(tp, TTY_BI); } 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" : "")); ttyld_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; *(u_short *)(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(sc->res.ctldev); 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); diff --git a/sys/dev/rc/rc.c b/sys/dev/rc/rc.c index 13d00ff3ffa8..1135faf8ec48 100644 --- a/sys/dev/rc/rc.c +++ b/sys/dev/rc/rc.c @@ -1,1587 +1,1570 @@ /* * Copyright (C) 1995 by Pavel Antonov, Moscow, Russia. * Copyright (C) 1995 by Andrey A. Chernov, Moscow, Russia. * Copyright (C) 2002 by John Baldwin * 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 AUTHORS ``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$ */ /* * SDL Communications Riscom/8 (based on Cirrus Logic CL-CD180) driver * */ /*#define RCDEBUG*/ #include "opt_tty.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IOBASE_ADDRS 14 #define DEV_TO_RC(dev) (struct rc_chans *)((dev)->si_drv1) #define TTY_TO_RC(tty) DEV_TO_RC((tty)->t_dev) #define rcin(sc, port) RC_IN(sc, port) #define rcout(sc, port, v) RC_OUT(sc, port, v) #define WAITFORCCR(sc, chan) rc_wait0((sc), (chan), __LINE__) #define CCRCMD(sc, chan, cmd) do { \ WAITFORCCR((sc), (chan)); \ rcout((sc), CD180_CCR, (cmd)); \ } while (0) #define RC_IBUFSIZE 256 #define RB_I_HIGH_WATER (TTYHOG - 2 * RC_IBUFSIZE) #define RC_OBUFSIZE 512 #define RC_IHIGHWATER (3 * RC_IBUFSIZE / 4) #define INPUT_FLAGS_SHIFT (2 * RC_IBUFSIZE) #define LOTS_OF_EVENTS 64 #define RC_FAKEID 0x10 #define CALLOUT(dev) (((intptr_t)(dev)->si_drv2) != 0) /* Per-channel structure */ struct rc_chans { struct rc_softc *rc_rcb; /* back ptr */ dev_t rc_dev; /* non-callout device */ dev_t rc_cdev; /* callout device */ u_short rc_flags; /* Misc. flags */ int rc_chan; /* Channel # */ u_char rc_ier; /* intr. enable reg */ u_char rc_msvr; /* modem sig. status */ u_char rc_cor2; /* options reg */ u_char rc_pendcmd; /* special cmd pending */ u_int rc_dtrwait; /* dtr timeout */ u_int rc_dcdwaits; /* how many waits DCD in open */ u_char rc_hotchar; /* end packed optimize */ struct tty rc_tp; /* tty struct */ u_char *rc_iptr; /* Chars input buffer */ u_char *rc_hiwat; /* hi-water mark */ u_char *rc_bufend; /* end of buffer */ u_char *rc_optr; /* ptr in output buf */ u_char *rc_obufend; /* end of output buf */ u_char rc_ibuf[4 * RC_IBUFSIZE]; /* input buffer */ u_char rc_obuf[RC_OBUFSIZE]; /* output buffer */ struct callout rc_dtrcallout; }; /* Per-board structure */ struct rc_softc { device_t sc_dev; struct resource *sc_irq; struct resource *sc_port[IOBASE_ADDRS]; int sc_irqrid; void *sc_hwicookie; bus_space_tag_t sc_bt; bus_space_handle_t sc_bh; u_int sc_unit; /* unit # */ u_char sc_dtr; /* DTR status */ int sc_opencount; int sc_scheduled_event; void *sc_swicookie; struct rc_chans sc_channels[CD180_NCHAN]; /* channels */ }; /* Static prototypes */ static void rc_release_resources(device_t dev); static void rc_intr(void *); static void rc_hwreset(struct rc_softc *, unsigned int); static int rc_test(struct rc_softc *); static void rc_discard_output(struct rc_chans *); static void rc_hardclose(struct rc_chans *); static int rc_modctl(struct rc_chans *, int, int); static void rc_start(struct tty *); static void rc_stop(struct tty *, int rw); static int rc_param(struct tty *, struct termios *); static void rc_pollcard(void *); static void rc_reinit(struct rc_softc *); #ifdef RCDEBUG static void printrcflags(); #endif static void rc_dtrwakeup(void *); -static void disc_optim(struct tty *tp, struct termios *t, struct rc_chans *); static void rc_wait0(struct rc_softc *sc, int chan, int line); static d_open_t rcopen; static d_close_t rcclose; static d_ioctl_t rcioctl; static struct cdevsw rc_cdevsw = { .d_version = D_VERSION, .d_open = rcopen, .d_close = rcclose, .d_ioctl = rcioctl, .d_name = "rc", .d_flags = D_TTY | D_NEEDGIANT, }; static devclass_t rc_devclass; /* Flags */ #define RC_DTR_OFF 0x0001 /* DTR wait, for close/open */ #define RC_ACTOUT 0x0002 /* Dial-out port active */ #define RC_RTSFLOW 0x0004 /* RTS flow ctl enabled */ #define RC_CTSFLOW 0x0008 /* CTS flow ctl enabled */ #define RC_DORXFER 0x0010 /* RXFER event planned */ #define RC_DOXXFER 0x0020 /* XXFER event planned */ #define RC_MODCHG 0x0040 /* Modem status changed */ #define RC_OSUSP 0x0080 /* Output suspended */ #define RC_OSBUSY 0x0100 /* start() routine in progress */ #define RC_WAS_BUFOVFL 0x0200 /* low-level buffer ovferflow */ #define RC_WAS_SILOVFL 0x0400 /* silo buffer overflow */ #define RC_SEND_RDY 0x0800 /* ready to send */ /* Table for translation of RCSR status bits to internal form */ static int rc_rcsrt[16] = { 0, TTY_OE, TTY_FE, TTY_FE|TTY_OE, TTY_PE, TTY_PE|TTY_OE, TTY_PE|TTY_FE, TTY_PE|TTY_FE|TTY_OE, TTY_BI, TTY_BI|TTY_OE, TTY_BI|TTY_FE, TTY_BI|TTY_FE|TTY_OE, TTY_BI|TTY_PE, TTY_BI|TTY_PE|TTY_OE, TTY_BI|TTY_PE|TTY_FE, TTY_BI|TTY_PE|TTY_FE|TTY_OE }; static int rc_ports[] = { 0x220, 0x240, 0x250, 0x260, 0x2a0, 0x2b0, 0x300, 0x320 }; static int iobase_addrs[IOBASE_ADDRS] = { 0, 0x400, 0x800, 0xc00, 0x1400, 0x1800, 0x1c00, 0x2000, 0x3000, 0x3400, 0x3800, 0x3c00, 0x4000, 0x8000 }; /**********************************************/ static int rc_probe(device_t dev) { u_int port; int i, found; /* * We don't know of any PnP ID's for these cards. */ if (isa_get_logicalid(dev) != 0) return (ENXIO); /* * We have to have an IO port hint that is valid. */ port = isa_get_port(dev); if (port == -1) return (ENXIO); found = 0; for (i = 0; i < sizeof(rc_ports) / sizeof(int); i++) if (rc_ports[i] == port) { found = 1; break; } if (!found) return (ENXIO); /* * We have to have an IRQ hint. */ if (isa_get_irq(dev) == -1) return (ENXIO); device_set_desc(dev, "SDL Riscom/8"); return (0); } static int rc_attach(device_t dev) { struct rc_chans *rc; struct tty *tp; struct rc_softc *sc; u_int port; int base, chan, error, i, x; dev_t cdev; sc = device_get_softc(dev); sc->sc_dev = dev; /* * We need to have IO ports. Lots of them. We need * the following ranges relative to the base port: * 0x0 - 0x10 * 0x400 - 0x410 * 0x800 - 0x810 * 0xc00 - 0xc10 * 0x1400 - 0x1410 * 0x1800 - 0x1810 * 0x1c00 - 0x1c10 * 0x2000 - 0x2010 * 0x3000 - 0x3010 * 0x3400 - 0x3410 * 0x3800 - 0x3810 * 0x3c00 - 0x3c10 * 0x4000 - 0x4010 * 0x8000 - 0x8010 */ port = isa_get_port(dev); for (i = 0; i < IOBASE_ADDRS; i++) if (bus_set_resource(dev, SYS_RES_IOPORT, i, port + iobase_addrs[i], 0x10) != 0) return (ENXIO); error = ENOMEM; for (i = 0; i < IOBASE_ADDRS; i++) { x = i; sc->sc_port[i] = bus_alloc_resource(dev, SYS_RES_IOPORT, &x, 0ul, ~0ul, 0x10, RF_ACTIVE); if (x != i) { device_printf(dev, "ioport %d was rid %d\n", i, x); goto fail; } if (sc->sc_port[i] == NULL) { device_printf(dev, "failed to alloc ioports %x-%x\n", port + iobase_addrs[i], port + iobase_addrs[i] + 0x10); goto fail; } } sc->sc_bt = rman_get_bustag(sc->sc_port[0]); sc->sc_bh = rman_get_bushandle(sc->sc_port[0]); sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_irqrid, RF_ACTIVE); if (sc->sc_irq == NULL) { device_printf(dev, "failed to alloc IRQ\n"); goto fail; } /* * Now do some actual tests to make sure it works. */ error = ENXIO; rcout(sc, CD180_PPRL, 0x22); /* Random values to Prescale reg. */ rcout(sc, CD180_PPRH, 0x11); if (rcin(sc, CD180_PPRL) != 0x22 || rcin(sc, CD180_PPRH) != 0x11) goto fail; if (rc_test(sc)) goto fail; /* * Ok, start actually hooking things up. */ sc->sc_unit = device_get_unit(dev); /*sc->sc_chipid = 0x10 + device_get_unit(dev);*/ device_printf(dev, "%d chans, firmware rev. %c\n", CD180_NCHAN, (rcin(sc, CD180_GFRCR) & 0xF) + 'A'); rc = sc->sc_channels; base = CD180_NCHAN * sc->sc_unit; for (chan = 0; chan < CD180_NCHAN; chan++, rc++) { rc->rc_rcb = sc; rc->rc_chan = chan; rc->rc_iptr = rc->rc_ibuf; rc->rc_bufend = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_hiwat = &rc->rc_ibuf[RC_IHIGHWATER]; rc->rc_optr = rc->rc_obufend = rc->rc_obuf; rc->rc_dtrwait = 3 * hz; callout_init(&rc->rc_dtrcallout, 0); tp = &rc->rc_tp; ttychars(tp); tp->t_lflag = tp->t_iflag = tp->t_oflag = 0; tp->t_cflag = TTYDEF_CFLAG; tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED; cdev = make_dev(&rc_cdevsw, chan + base, UID_ROOT, GID_WHEEL, 0600, "ttym%d", chan + base); cdev->si_drv1 = rc; cdev->si_drv2 = 0; cdev->si_tty = tp; rc->rc_dev = cdev; cdev = make_dev(&rc_cdevsw, chan + base + 128, UID_UUCP, GID_DIALER, 0660, "cuam%d", chan + base); cdev->si_drv1 = rc; cdev->si_drv2 = (void *)1; cdev->si_tty = tp; rc->rc_cdev = cdev; } error = bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_TTY, rc_intr, sc, &sc->sc_hwicookie); if (error) { device_printf(dev, "failed to register interrupt handler\n"); goto fail; } swi_add(&tty_ithd, "tty:rc", rc_pollcard, sc, SWI_TTY, 0, &sc->sc_swicookie); return (0); fail: rc_release_resources(dev); return (error); } static int rc_detach(device_t dev) { struct rc_softc *sc; struct rc_chans *rc; int error, i, s; sc = device_get_softc(dev); if (sc->sc_opencount > 0) return (EBUSY); sc->sc_opencount = -1; rc = sc->sc_channels; for (i = 0; i < CD180_NCHAN; i++, rc++) { destroy_dev(rc->rc_dev); destroy_dev(rc->rc_cdev); } rc = sc->sc_channels; s = splsoftclock(); for (i = 0; i < CD180_NCHAN; i++) { if ((rc->rc_flags & RC_DTR_OFF) && !callout_stop(&rc->rc_dtrcallout)) tsleep(&rc->rc_dtrwait, TTIPRI, "rcdtrdet", 0); } error = bus_teardown_intr(dev, sc->sc_irq, sc->sc_hwicookie); if (error) device_printf(dev, "failed to deregister interrupt handler\n"); ithread_remove_handler(sc->sc_swicookie); rc_release_resources(dev); return (0); } static void rc_release_resources(device_t dev) { struct rc_softc *sc; int i; sc = device_get_softc(dev); if (sc->sc_irq != NULL) { bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irqrid, sc->sc_irq); sc->sc_irq = NULL; } for (i = 0; i < IOBASE_ADDRS; i++) { if (sc->sc_port[i] == NULL) break; bus_release_resource(dev, SYS_RES_IOPORT, i, sc->sc_port[i]); sc->sc_port[i] = NULL; } } /* RC interrupt handling */ static void rc_intr(void *arg) { struct rc_softc *sc; struct rc_chans *rc; int resid, chan; u_char val, iack, bsr, ucnt, *optr; int good_data, t_state; sc = (struct rc_softc *)arg; bsr = ~(rcin(sc, RC_BSR)); if (!(bsr & (RC_BSR_TOUT|RC_BSR_RXINT|RC_BSR_TXINT|RC_BSR_MOINT))) { device_printf(sc->sc_dev, "extra interrupt\n"); rcout(sc, CD180_EOIR, 0); return; } while (bsr & (RC_BSR_TOUT|RC_BSR_RXINT|RC_BSR_TXINT|RC_BSR_MOINT)) { #ifdef RCDEBUG_DETAILED device_printf(sc->sc_dev, "intr (%p) %s%s%s%s\n", arg, bsr, (bsr & RC_BSR_TOUT)?"TOUT ":"", (bsr & RC_BSR_RXINT)?"RXINT ":"", (bsr & RC_BSR_TXINT)?"TXINT ":"", (bsr & RC_BSR_MOINT)?"MOINT":""); #endif if (bsr & RC_BSR_TOUT) { device_printf(sc->sc_dev, "hardware failure, reset board\n"); rcout(sc, RC_CTOUT, 0); rc_reinit(sc); return; } if (bsr & RC_BSR_RXINT) { iack = rcin(sc, RC_PILR_RX); good_data = (iack == (GIVR_IT_RGDI | RC_FAKEID)); if (!good_data && iack != (GIVR_IT_REI | RC_FAKEID)) { device_printf(sc->sc_dev, "fake rxint: %02x\n", iack); goto more_intrs; } chan = ((rcin(sc, CD180_GICR) & GICR_CHAN) >> GICR_LSH); rc = &sc->sc_channels[chan]; t_state = rc->rc_tp.t_state; /* Do RTS flow control stuff */ if ( (rc->rc_flags & RC_RTSFLOW) || !(t_state & TS_ISOPEN) ) { if ( ( !(t_state & TS_ISOPEN) || (t_state & TS_TBLOCK) ) && (rc->rc_msvr & MSVR_RTS) ) rcout(sc, CD180_MSVR, rc->rc_msvr &= ~MSVR_RTS); else if (!(rc->rc_msvr & MSVR_RTS)) rcout(sc, CD180_MSVR, rc->rc_msvr |= MSVR_RTS); } ucnt = rcin(sc, CD180_RDCR) & 0xF; resid = 0; if (t_state & TS_ISOPEN) { /* check for input buffer overflow */ if ((rc->rc_iptr + ucnt) >= rc->rc_bufend) { resid = ucnt; ucnt = rc->rc_bufend - rc->rc_iptr; resid -= ucnt; if (!(rc->rc_flags & RC_WAS_BUFOVFL)) { rc->rc_flags |= RC_WAS_BUFOVFL; sc->sc_scheduled_event++; } } optr = rc->rc_iptr; /* check foor good data */ if (good_data) { while (ucnt-- > 0) { val = rcin(sc, CD180_RDR); optr[0] = val; optr[INPUT_FLAGS_SHIFT] = 0; optr++; sc->sc_scheduled_event++; if (val != 0 && val == rc->rc_hotchar) swi_sched(sc->sc_swicookie, 0); } } else { /* Store also status data */ while (ucnt-- > 0) { iack = rcin(sc, CD180_RCSR); if (iack & RCSR_Timeout) break; if ( (iack & RCSR_OE) && !(rc->rc_flags & RC_WAS_SILOVFL)) { rc->rc_flags |= RC_WAS_SILOVFL; sc->sc_scheduled_event++; } val = rcin(sc, CD180_RDR); /* Don't store PE if IGNPAR and BREAK if IGNBRK, this hack allows "raw" tty optimization works even if IGN* is set. */ if ( !(iack & (RCSR_PE|RCSR_FE|RCSR_Break)) || ((!(iack & (RCSR_PE|RCSR_FE)) || !(rc->rc_tp.t_iflag & IGNPAR)) && (!(iack & RCSR_Break) || !(rc->rc_tp.t_iflag & IGNBRK)))) { if ( (iack & (RCSR_PE|RCSR_FE)) && (t_state & TS_CAN_BYPASS_L_RINT) && ((iack & RCSR_FE) || ((iack & RCSR_PE) && (rc->rc_tp.t_iflag & INPCK)))) val = 0; else if (val != 0 && val == rc->rc_hotchar) swi_sched(sc->sc_swicookie, 0); optr[0] = val; optr[INPUT_FLAGS_SHIFT] = iack; optr++; sc->sc_scheduled_event++; } } } rc->rc_iptr = optr; rc->rc_flags |= RC_DORXFER; } else resid = ucnt; /* Clear FIFO if necessary */ while (resid-- > 0) { if (!good_data) iack = rcin(sc, CD180_RCSR); else iack = 0; if (iack & RCSR_Timeout) break; (void) rcin(sc, CD180_RDR); } goto more_intrs; } if (bsr & RC_BSR_MOINT) { iack = rcin(sc, RC_PILR_MODEM); if (iack != (GIVR_IT_MSCI | RC_FAKEID)) { device_printf(sc->sc_dev, "fake moint: %02x\n", iack); goto more_intrs; } chan = ((rcin(sc, CD180_GICR) & GICR_CHAN) >> GICR_LSH); rc = &sc->sc_channels[chan]; iack = rcin(sc, CD180_MCR); rc->rc_msvr = rcin(sc, CD180_MSVR); rcout(sc, CD180_MCR, 0); #ifdef RCDEBUG printrcflags(rc, "moint"); #endif if (rc->rc_flags & RC_CTSFLOW) { if (rc->rc_msvr & MSVR_CTS) rc->rc_flags |= RC_SEND_RDY; else rc->rc_flags &= ~RC_SEND_RDY; } else rc->rc_flags |= RC_SEND_RDY; if ((iack & MCR_CDchg) && !(rc->rc_flags & RC_MODCHG)) { sc->sc_scheduled_event += LOTS_OF_EVENTS; rc->rc_flags |= RC_MODCHG; swi_sched(sc->sc_swicookie, 0); } goto more_intrs; } if (bsr & RC_BSR_TXINT) { iack = rcin(sc, RC_PILR_TX); if (iack != (GIVR_IT_TDI | RC_FAKEID)) { device_printf(sc->sc_dev, "fake txint: %02x\n", iack); goto more_intrs; } chan = ((rcin(sc, CD180_GICR) & GICR_CHAN) >> GICR_LSH); rc = &sc->sc_channels[chan]; if ( (rc->rc_flags & RC_OSUSP) || !(rc->rc_flags & RC_SEND_RDY) ) goto more_intrs; /* Handle breaks and other stuff */ if (rc->rc_pendcmd) { rcout(sc, CD180_COR2, rc->rc_cor2 |= COR2_ETC); rcout(sc, CD180_TDR, CD180_C_ESC); rcout(sc, CD180_TDR, rc->rc_pendcmd); rcout(sc, CD180_COR2, rc->rc_cor2 &= ~COR2_ETC); rc->rc_pendcmd = 0; goto more_intrs; } optr = rc->rc_optr; resid = rc->rc_obufend - optr; if (resid > CD180_NFIFO) resid = CD180_NFIFO; while (resid-- > 0) rcout(sc, CD180_TDR, *optr++); rc->rc_optr = optr; /* output completed? */ if (optr >= rc->rc_obufend) { rcout(sc, CD180_IER, rc->rc_ier &= ~IER_TxRdy); #ifdef RCDEBUG device_printf(sc->sc_dev, "channel %d: output completed\n", rc->rc_chan); #endif if (!(rc->rc_flags & RC_DOXXFER)) { sc->sc_scheduled_event += LOTS_OF_EVENTS; rc->rc_flags |= RC_DOXXFER; swi_sched(sc->sc_swicookie, 0); } } } more_intrs: rcout(sc, CD180_EOIR, 0); /* end of interrupt */ rcout(sc, RC_CTOUT, 0); bsr = ~(rcin(sc, RC_BSR)); } } /* Feed characters to output buffer */ static void rc_start(struct tty *tp) { struct rc_softc *sc; struct rc_chans *rc; int s; rc = TTY_TO_RC(tp); if (rc->rc_flags & RC_OSBUSY) return; sc = rc->rc_rcb; s = spltty(); rc->rc_flags |= RC_OSBUSY; critical_enter(); if (tp->t_state & TS_TTSTOP) rc->rc_flags |= RC_OSUSP; else rc->rc_flags &= ~RC_OSUSP; /* Do RTS flow control stuff */ if ( (rc->rc_flags & RC_RTSFLOW) && (tp->t_state & TS_TBLOCK) && (rc->rc_msvr & MSVR_RTS) ) { rcout(sc, CD180_CAR, rc->rc_chan); rcout(sc, CD180_MSVR, rc->rc_msvr &= ~MSVR_RTS); } else if (!(rc->rc_msvr & MSVR_RTS)) { rcout(sc, CD180_CAR, rc->rc_chan); rcout(sc, CD180_MSVR, rc->rc_msvr |= MSVR_RTS); } critical_exit(); if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP)) goto out; #ifdef RCDEBUG printrcflags(rc, "rcstart"); #endif ttwwakeup(tp); #ifdef RCDEBUG printf("rcstart: outq = %d obuf = %d\n", tp->t_outq.c_cc, rc->rc_obufend - rc->rc_optr); #endif if (tp->t_state & TS_BUSY) goto out; /* output still in progress ... */ if (tp->t_outq.c_cc > 0) { u_int ocnt; tp->t_state |= TS_BUSY; ocnt = q_to_b(&tp->t_outq, rc->rc_obuf, sizeof rc->rc_obuf); critical_enter(); rc->rc_optr = rc->rc_obuf; rc->rc_obufend = rc->rc_optr + ocnt; critical_exit(); if (!(rc->rc_ier & IER_TxRdy)) { #ifdef RCDEBUG device_printf(sc->sc_dev, "channel %d: rcstart enable txint\n", rc->rc_chan); #endif rcout(sc, CD180_CAR, rc->rc_chan); rcout(sc, CD180_IER, rc->rc_ier |= IER_TxRdy); } } out: rc->rc_flags &= ~RC_OSBUSY; (void) splx(s); } /* Handle delayed events. */ void rc_pollcard(void *arg) { struct rc_softc *sc; struct rc_chans *rc; struct tty *tp; u_char *tptr, *eptr; int chan, icnt; sc = (struct rc_softc *)arg; if (sc->sc_scheduled_event == 0) return; do { rc = sc->sc_channels; for (chan = 0; chan < CD180_NCHAN; rc++, chan++) { tp = &rc->rc_tp; #ifdef RCDEBUG if (rc->rc_flags & (RC_DORXFER|RC_DOXXFER|RC_MODCHG| RC_WAS_BUFOVFL|RC_WAS_SILOVFL)) printrcflags(rc, "rcevent"); #endif if (rc->rc_flags & RC_WAS_BUFOVFL) { critical_enter(); rc->rc_flags &= ~RC_WAS_BUFOVFL; sc->sc_scheduled_event--; critical_exit(); device_printf(sc->sc_dev, "channel %d: interrupt-level buffer overflow\n", chan); } if (rc->rc_flags & RC_WAS_SILOVFL) { critical_enter(); rc->rc_flags &= ~RC_WAS_SILOVFL; sc->sc_scheduled_event--; critical_exit(); device_printf(sc->sc_dev, "channel %d: silo overflow\n", chan); } if (rc->rc_flags & RC_MODCHG) { critical_enter(); rc->rc_flags &= ~RC_MODCHG; sc->sc_scheduled_event -= LOTS_OF_EVENTS; critical_exit(); ttyld_modem(tp, !!(rc->rc_msvr & MSVR_CD)); } if (rc->rc_flags & RC_DORXFER) { critical_enter(); rc->rc_flags &= ~RC_DORXFER; eptr = rc->rc_iptr; if (rc->rc_bufend == &rc->rc_ibuf[2 * RC_IBUFSIZE]) tptr = &rc->rc_ibuf[RC_IBUFSIZE]; else tptr = rc->rc_ibuf; icnt = eptr - tptr; if (icnt > 0) { if (rc->rc_bufend == &rc->rc_ibuf[2 * RC_IBUFSIZE]) { rc->rc_iptr = rc->rc_ibuf; rc->rc_bufend = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_hiwat = &rc->rc_ibuf[RC_IHIGHWATER]; } else { rc->rc_iptr = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_bufend = &rc->rc_ibuf[2 * RC_IBUFSIZE]; rc->rc_hiwat = &rc->rc_ibuf[RC_IBUFSIZE + RC_IHIGHWATER]; } if ( (rc->rc_flags & RC_RTSFLOW) && (tp->t_state & TS_ISOPEN) && !(tp->t_state & TS_TBLOCK) && !(rc->rc_msvr & MSVR_RTS) ) { rcout(sc, CD180_CAR, chan); rcout(sc, CD180_MSVR, rc->rc_msvr |= MSVR_RTS); } sc->sc_scheduled_event -= icnt; } critical_exit(); if (icnt <= 0 || !(tp->t_state & TS_ISOPEN)) goto done1; if ( (tp->t_state & TS_CAN_BYPASS_L_RINT) && !(tp->t_state & TS_LOCAL)) { if ((tp->t_rawq.c_cc + icnt) >= RB_I_HIGH_WATER && ((rc->rc_flags & RC_RTSFLOW) || (tp->t_iflag & IXOFF)) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); tk_nin += icnt; tk_rawcc += icnt; tp->t_rawcc += icnt; if (b_to_q(tptr, icnt, &tp->t_rawq)) device_printf(sc->sc_dev, "channel %d: tty-level buffer overflow\n", chan); ttwakeup(tp); if ((tp->t_state & TS_TTSTOP) && ((tp->t_iflag & IXANY) || (tp->t_cc[VSTART] == tp->t_cc[VSTOP]))) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; rc_start(tp); } } else { for (; tptr < eptr; tptr++) ttyld_rint(tp, (tptr[0] | rc_rcsrt[tptr[INPUT_FLAGS_SHIFT] & 0xF])); } done1: ; } if (rc->rc_flags & RC_DOXXFER) { critical_enter(); sc->sc_scheduled_event -= LOTS_OF_EVENTS; rc->rc_flags &= ~RC_DOXXFER; rc->rc_tp.t_state &= ~TS_BUSY; critical_exit(); ttyld_start(tp); } if (sc->sc_scheduled_event == 0) break; } } while (sc->sc_scheduled_event >= LOTS_OF_EVENTS); } static void rc_stop(struct tty *tp, int rw) { struct rc_softc *sc; struct rc_chans *rc; u_char *tptr, *eptr; rc = TTY_TO_RC(tp); sc = rc->rc_rcb; #ifdef RCDEBUG device_printf(sc->sc_dev, "channel %d: rc_stop %s%s\n", rc->rc_chan, (rw & FWRITE)?"FWRITE ":"", (rw & FREAD)?"FREAD":""); #endif if (rw & FWRITE) rc_discard_output(rc); critical_enter(); if (rw & FREAD) { rc->rc_flags &= ~RC_DORXFER; eptr = rc->rc_iptr; if (rc->rc_bufend == &rc->rc_ibuf[2 * RC_IBUFSIZE]) { tptr = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_iptr = &rc->rc_ibuf[RC_IBUFSIZE]; } else { tptr = rc->rc_ibuf; rc->rc_iptr = rc->rc_ibuf; } sc->sc_scheduled_event -= eptr - tptr; } if (tp->t_state & TS_TTSTOP) rc->rc_flags |= RC_OSUSP; else rc->rc_flags &= ~RC_OSUSP; critical_exit(); } static int rcopen(dev_t dev, int flag, int mode, d_thread_t *td) { struct rc_softc *sc; struct rc_chans *rc; struct tty *tp; int s, error = 0; rc = DEV_TO_RC(dev); sc = rc->rc_rcb; tp = &rc->rc_tp; if (sc->sc_opencount < 0) return (ENXIO); sc->sc_opencount++; #ifdef RCDEBUG device_printf(sc->sc_dev, "channel %d: rcopen: dev %p\n", rc->rc_chan, dev); #endif s = spltty(); again: while (rc->rc_flags & RC_DTR_OFF) { error = tsleep(&(rc->rc_dtrwait), TTIPRI | PCATCH, "rcdtr", 0); if (error != 0) goto out; } if (tp->t_state & TS_ISOPEN) { if (CALLOUT(dev)) { if (!(rc->rc_flags & RC_ACTOUT)) { error = EBUSY; goto out; } } else { if (rc->rc_flags & RC_ACTOUT) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&rc->rc_rcb, TTIPRI|PCATCH, "rcbi", 0); if (error) goto out; goto again; } } if (tp->t_state & TS_XCLUDE && suser(td)) { error = EBUSY; goto out; } } else { tp->t_oproc = rc_start; tp->t_param = rc_param; tp->t_stop = rc_stop; tp->t_dev = dev; if (CALLOUT(dev)) tp->t_cflag |= CLOCAL; else tp->t_cflag &= ~CLOCAL; error = rc_param(tp, &tp->t_termios); if (error) goto out; (void) rc_modctl(rc, TIOCM_RTS|TIOCM_DTR, DMSET); if ((rc->rc_msvr & MSVR_CD) || CALLOUT(dev)) ttyld_modem(tp, 1); } if (!(tp->t_state & TS_CARR_ON) && !CALLOUT(dev) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { rc->rc_dcdwaits++; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "rcdcd", 0); rc->rc_dcdwaits--; if (error != 0) goto out; goto again; } error = ttyld_open(tp, dev); - disc_optim(tp, &tp->t_termios, rc); + rc->rc_hotchar = ttyldoptim(tp); if ((tp->t_state & TS_ISOPEN) && CALLOUT(dev)) rc->rc_flags |= RC_ACTOUT; out: (void) splx(s); if(rc->rc_dcdwaits == 0 && !(tp->t_state & TS_ISOPEN)) rc_hardclose(rc); return error; } static int rcclose(dev_t dev, int flag, int mode, d_thread_t *td) { struct rc_softc *sc; struct rc_chans *rc; struct tty *tp; int s; rc = DEV_TO_RC(dev); sc = rc->rc_rcb; tp = &rc->rc_tp; #ifdef RCDEBUG device_printf(sc->sc_dev, "channel %d: rcclose dev %p\n", rc->rc_chan, dev); #endif s = spltty(); ttyld_close(tp, flag); - disc_optim(tp, &tp->t_termios, rc); + rc->rc_hotchar = ttyldoptim(tp); rc_hardclose(rc); ttyclose(tp); splx(s); KASSERT(sc->sc_opencount > 0, ("rcclose: non-positive open count")); sc->sc_opencount--; return 0; } static void rc_hardclose(struct rc_chans *rc) { struct rc_softc *sc; struct tty *tp; int s; tp = &rc->rc_tp; sc = rc->rc_rcb; s = spltty(); rcout(sc, CD180_CAR, rc->rc_chan); /* Disable rx/tx intrs */ rcout(sc, CD180_IER, rc->rc_ier = 0); if ( (tp->t_cflag & HUPCL) || (!(rc->rc_flags & RC_ACTOUT) && !(rc->rc_msvr & MSVR_CD) && !(tp->t_cflag & CLOCAL)) || !(tp->t_state & TS_ISOPEN) ) { CCRCMD(sc, rc->rc_chan, CCR_ResetChan); WAITFORCCR(sc, rc->rc_chan); (void) rc_modctl(rc, TIOCM_RTS, DMSET); if (rc->rc_dtrwait) { callout_reset(&rc->rc_dtrcallout, rc->rc_dtrwait, rc_dtrwakeup, rc); rc->rc_flags |= RC_DTR_OFF; } } rc->rc_flags &= ~RC_ACTOUT; wakeup( &rc->rc_rcb); /* wake bi */ wakeup(TSA_CARR_ON(tp)); (void) splx(s); } /* Reset the bastard */ static void rc_hwreset(struct rc_softc *sc, u_int chipid) { CCRCMD(sc, -1, CCR_HWRESET); /* Hardware reset */ DELAY(20000); WAITFORCCR(sc, -1); rcout(sc, RC_CTOUT, 0); /* Clear timeout */ rcout(sc, CD180_GIVR, chipid); rcout(sc, CD180_GICR, 0); /* Set Prescaler Registers (1 msec) */ rcout(sc, CD180_PPRL, ((RC_OSCFREQ + 999) / 1000) & 0xFF); rcout(sc, CD180_PPRH, ((RC_OSCFREQ + 999) / 1000) >> 8); /* Initialize Priority Interrupt Level Registers */ rcout(sc, CD180_PILR1, RC_PILR_MODEM); rcout(sc, CD180_PILR2, RC_PILR_TX); rcout(sc, CD180_PILR3, RC_PILR_RX); /* Reset DTR */ rcout(sc, RC_DTREG, ~0); } /* Set channel parameters */ static int rc_param(struct tty *tp, struct termios *ts) { struct rc_softc *sc; struct rc_chans *rc; int idivs, odivs, s, val, cflag, iflag, lflag, inpflow; if ( ts->c_ospeed < 0 || ts->c_ospeed > 76800 || ts->c_ispeed < 0 || ts->c_ispeed > 76800 ) return (EINVAL); if (ts->c_ispeed == 0) ts->c_ispeed = ts->c_ospeed; odivs = RC_BRD(ts->c_ospeed); idivs = RC_BRD(ts->c_ispeed); rc = TTY_TO_RC(tp); sc = rc->rc_rcb; s = spltty(); /* Select channel */ rcout(sc, CD180_CAR, rc->rc_chan); /* If speed == 0, hangup line */ if (ts->c_ospeed == 0) { CCRCMD(sc, rc->rc_chan, CCR_ResetChan); WAITFORCCR(sc, rc->rc_chan); (void) rc_modctl(rc, TIOCM_DTR, DMBIC); } tp->t_state &= ~TS_CAN_BYPASS_L_RINT; cflag = ts->c_cflag; iflag = ts->c_iflag; lflag = ts->c_lflag; if (idivs > 0) { rcout(sc, CD180_RBPRL, idivs & 0xFF); rcout(sc, CD180_RBPRH, idivs >> 8); } if (odivs > 0) { rcout(sc, CD180_TBPRL, odivs & 0xFF); rcout(sc, CD180_TBPRH, odivs >> 8); } /* set timeout value */ if (ts->c_ispeed > 0) { int itm = ts->c_ispeed > 2400 ? 5 : 10000 / ts->c_ispeed + 1; if ( !(lflag & ICANON) && ts->c_cc[VMIN] != 0 && ts->c_cc[VTIME] != 0 && ts->c_cc[VTIME] * 10 > itm) itm = ts->c_cc[VTIME] * 10; rcout(sc, CD180_RTPR, itm <= 255 ? itm : 255); } switch (cflag & CSIZE) { case CS5: val = COR1_5BITS; break; case CS6: val = COR1_6BITS; break; case CS7: val = COR1_7BITS; break; default: case CS8: val = COR1_8BITS; break; } if (cflag & PARENB) { val |= COR1_NORMPAR; if (cflag & PARODD) val |= COR1_ODDP; if (!(cflag & INPCK)) val |= COR1_Ignore; } else val |= COR1_Ignore; if (cflag & CSTOPB) val |= COR1_2SB; rcout(sc, CD180_COR1, val); /* Set FIFO threshold */ val = ts->c_ospeed <= 4800 ? 1 : CD180_NFIFO / 2; inpflow = 0; if ( (iflag & IXOFF) && ( ts->c_cc[VSTOP] != _POSIX_VDISABLE && ( ts->c_cc[VSTART] != _POSIX_VDISABLE || (iflag & IXANY) ) ) ) { inpflow = 1; val |= COR3_SCDE|COR3_FCT; } rcout(sc, CD180_COR3, val); /* Initialize on-chip automatic flow control */ val = 0; rc->rc_flags &= ~(RC_CTSFLOW|RC_SEND_RDY); if (cflag & CCTS_OFLOW) { rc->rc_flags |= RC_CTSFLOW; val |= COR2_CtsAE; } else rc->rc_flags |= RC_SEND_RDY; if (tp->t_state & TS_TTSTOP) rc->rc_flags |= RC_OSUSP; else rc->rc_flags &= ~RC_OSUSP; if (cflag & CRTS_IFLOW) rc->rc_flags |= RC_RTSFLOW; else rc->rc_flags &= ~RC_RTSFLOW; if (inpflow) { if (ts->c_cc[VSTART] != _POSIX_VDISABLE) rcout(sc, CD180_SCHR1, ts->c_cc[VSTART]); rcout(sc, CD180_SCHR2, ts->c_cc[VSTOP]); val |= COR2_TxIBE; if (iflag & IXANY) val |= COR2_IXM; } rcout(sc, CD180_COR2, rc->rc_cor2 = val); CCRCMD(sc, rc->rc_chan, CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3); - disc_optim(tp, ts, rc); + rc->rc_hotchar = ttyldoptim(tp); /* modem ctl */ val = cflag & CLOCAL ? 0 : MCOR1_CDzd; if (cflag & CCTS_OFLOW) val |= MCOR1_CTSzd; rcout(sc, CD180_MCOR1, val); val = cflag & CLOCAL ? 0 : MCOR2_CDod; if (cflag & CCTS_OFLOW) val |= MCOR2_CTSod; rcout(sc, CD180_MCOR2, val); /* enable i/o and interrupts */ CCRCMD(sc, rc->rc_chan, CCR_XMTREN | ((cflag & CREAD) ? CCR_RCVREN : CCR_RCVRDIS)); WAITFORCCR(sc, rc->rc_chan); rc->rc_ier = cflag & CLOCAL ? 0 : IER_CD; if (cflag & CCTS_OFLOW) rc->rc_ier |= IER_CTS; if (cflag & CREAD) rc->rc_ier |= IER_RxData; if (tp->t_state & TS_BUSY) rc->rc_ier |= IER_TxRdy; if (ts->c_ospeed != 0) rc_modctl(rc, TIOCM_DTR, DMBIS); if ((cflag & CCTS_OFLOW) && (rc->rc_msvr & MSVR_CTS)) rc->rc_flags |= RC_SEND_RDY; rcout(sc, CD180_IER, rc->rc_ier); (void) splx(s); return 0; } /* Re-initialize board after bogus interrupts */ static void rc_reinit(struct rc_softc *sc) { struct rc_chans *rc; int i; rc_hwreset(sc, RC_FAKEID); rc = sc->sc_channels; for (i = 0; i < CD180_NCHAN; i++, rc++) (void) rc_param(&rc->rc_tp, &rc->rc_tp.t_termios); } static int rcioctl(dev_t dev, u_long cmd, caddr_t data, int flag, d_thread_t *td) { struct rc_chans *rc; struct tty *tp; int s, error; rc = DEV_TO_RC(dev); tp = &rc->rc_tp; error = ttyioctl(dev, cmd, data, flag, td); - disc_optim(tp, &tp->t_termios, rc); + rc->rc_hotchar = ttyldoptim(tp); if (error != ENOTTY) return (error); s = spltty(); switch (cmd) { case TIOCSBRK: rc->rc_pendcmd = CD180_C_SBRK; break; case TIOCCBRK: rc->rc_pendcmd = CD180_C_EBRK; break; case TIOCSDTR: (void) rc_modctl(rc, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void) rc_modctl(rc, TIOCM_DTR, DMBIC); break; case TIOCMGET: *(int *) data = rc_modctl(rc, 0, DMGET); break; case TIOCMSET: (void) rc_modctl(rc, *(int *) data, DMSET); break; case TIOCMBIC: (void) rc_modctl(rc, *(int *) data, DMBIC); break; case TIOCMBIS: (void) rc_modctl(rc, *(int *) data, DMBIS); break; case TIOCMSDTRWAIT: error = suser(td); if (error != 0) { splx(s); return (error); } rc->rc_dtrwait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = rc->rc_dtrwait * 100 / hz; break; default: (void) splx(s); return ENOTTY; } (void) splx(s); return 0; } /* Modem control routines */ static int rc_modctl(struct rc_chans *rc, int bits, int cmd) { struct rc_softc *sc; u_char *dtr; u_char msvr; sc = rc->rc_rcb; dtr = &sc->sc_dtr; rcout(sc, CD180_CAR, rc->rc_chan); switch (cmd) { case DMSET: rcout(sc, RC_DTREG, (bits & TIOCM_DTR) ? ~(*dtr |= 1 << rc->rc_chan) : ~(*dtr &= ~(1 << rc->rc_chan))); msvr = rcin(sc, CD180_MSVR); if (bits & TIOCM_RTS) msvr |= MSVR_RTS; else msvr &= ~MSVR_RTS; if (bits & TIOCM_DTR) msvr |= MSVR_DTR; else msvr &= ~MSVR_DTR; rcout(sc, CD180_MSVR, msvr); break; case DMBIS: if (bits & TIOCM_DTR) rcout(sc, RC_DTREG, ~(*dtr |= 1 << rc->rc_chan)); msvr = rcin(sc, CD180_MSVR); if (bits & TIOCM_RTS) msvr |= MSVR_RTS; if (bits & TIOCM_DTR) msvr |= MSVR_DTR; rcout(sc, CD180_MSVR, msvr); break; case DMGET: bits = TIOCM_LE; msvr = rc->rc_msvr = rcin(sc, CD180_MSVR); if (msvr & MSVR_RTS) bits |= TIOCM_RTS; if (msvr & MSVR_CTS) bits |= TIOCM_CTS; if (msvr & MSVR_DSR) bits |= TIOCM_DSR; if (msvr & MSVR_DTR) bits |= TIOCM_DTR; if (msvr & MSVR_CD) bits |= TIOCM_CD; if (~rcin(sc, RC_RIREG) & (1 << rc->rc_chan)) bits |= TIOCM_RI; return bits; case DMBIC: if (bits & TIOCM_DTR) rcout(sc, RC_DTREG, ~(*dtr &= ~(1 << rc->rc_chan))); msvr = rcin(sc, CD180_MSVR); if (bits & TIOCM_RTS) msvr &= ~MSVR_RTS; if (bits & TIOCM_DTR) msvr &= ~MSVR_DTR; rcout(sc, CD180_MSVR, msvr); break; } rc->rc_msvr = rcin(sc, CD180_MSVR); return 0; } #define ERR(s) do { \ device_printf(sc->sc_dev, "%s", ""); \ printf s ; \ printf("\n"); \ (void) splx(old_level); \ return 1; \ } while (0) /* Test the board. */ int rc_test(struct rc_softc *sc) { int chan = 0; int i = 0, rcnt, old_level; unsigned int iack, chipid; unsigned short divs; static u_char ctest[] = "\377\125\252\045\244\0\377"; #define CTLEN 8 struct rtest { u_char txbuf[CD180_NFIFO]; /* TX buffer */ u_char rxbuf[CD180_NFIFO]; /* RX buffer */ int rxptr; /* RX pointer */ int txptr; /* TX pointer */ } tchans[CD180_NCHAN]; old_level = spltty(); chipid = RC_FAKEID; /* First, reset board to inital state */ rc_hwreset(sc, chipid); divs = RC_BRD(19200); /* Initialize channels */ for (chan = 0; chan < CD180_NCHAN; chan++) { /* Select and reset channel */ rcout(sc, CD180_CAR, chan); CCRCMD(sc, chan, CCR_ResetChan); WAITFORCCR(sc, chan); /* Set speed */ rcout(sc, CD180_RBPRL, divs & 0xFF); rcout(sc, CD180_RBPRH, divs >> 8); rcout(sc, CD180_TBPRL, divs & 0xFF); rcout(sc, CD180_TBPRH, divs >> 8); /* set timeout value */ rcout(sc, CD180_RTPR, 0); /* Establish local loopback */ rcout(sc, CD180_COR1, COR1_NOPAR | COR1_8BITS | COR1_1SB); rcout(sc, CD180_COR2, COR2_LLM); rcout(sc, CD180_COR3, CD180_NFIFO); CCRCMD(sc, chan, CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3); CCRCMD(sc, chan, CCR_RCVREN | CCR_XMTREN); WAITFORCCR(sc, chan); rcout(sc, CD180_MSVR, MSVR_RTS); /* Fill TXBUF with test data */ for (i = 0; i < CD180_NFIFO; i++) { tchans[chan].txbuf[i] = ctest[i]; tchans[chan].rxbuf[i] = 0; } tchans[chan].txptr = tchans[chan].rxptr = 0; /* Now, start transmit */ rcout(sc, CD180_IER, IER_TxMpty|IER_RxData); } /* Pseudo-interrupt poll stuff */ for (rcnt = 10000; rcnt-- > 0; rcnt--) { i = ~(rcin(sc, RC_BSR)); if (i & RC_BSR_TOUT) ERR(("BSR timeout bit set\n")); else if (i & RC_BSR_TXINT) { iack = rcin(sc, RC_PILR_TX); if (iack != (GIVR_IT_TDI | chipid)) ERR(("Bad TX intr ack (%02x != %02x)\n", iack, GIVR_IT_TDI | chipid)); chan = (rcin(sc, CD180_GICR) & GICR_CHAN) >> GICR_LSH; /* If no more data to transmit, disable TX intr */ if (tchans[chan].txptr >= CD180_NFIFO) { iack = rcin(sc, CD180_IER); rcout(sc, CD180_IER, iack & ~IER_TxMpty); } else { for (iack = tchans[chan].txptr; iack < CD180_NFIFO; iack++) rcout(sc, CD180_TDR, tchans[chan].txbuf[iack]); tchans[chan].txptr = iack; } rcout(sc, CD180_EOIR, 0); } else if (i & RC_BSR_RXINT) { u_char ucnt; iack = rcin(sc, RC_PILR_RX); if (iack != (GIVR_IT_RGDI | chipid) && iack != (GIVR_IT_REI | chipid)) ERR(("Bad RX intr ack (%02x != %02x)\n", iack, GIVR_IT_RGDI | chipid)); chan = (rcin(sc, CD180_GICR) & GICR_CHAN) >> GICR_LSH; ucnt = rcin(sc, CD180_RDCR) & 0xF; while (ucnt-- > 0) { iack = rcin(sc, CD180_RCSR); if (iack & RCSR_Timeout) break; if (iack & 0xF) ERR(("Bad char chan %d (RCSR = %02X)\n", chan, iack)); if (tchans[chan].rxptr > CD180_NFIFO) ERR(("Got extra chars chan %d\n", chan)); tchans[chan].rxbuf[tchans[chan].rxptr++] = rcin(sc, CD180_RDR); } rcout(sc, CD180_EOIR, 0); } rcout(sc, RC_CTOUT, 0); for (iack = chan = 0; chan < CD180_NCHAN; chan++) if (tchans[chan].rxptr >= CD180_NFIFO) iack++; if (iack == CD180_NCHAN) break; } for (chan = 0; chan < CD180_NCHAN; chan++) { /* Select and reset channel */ rcout(sc, CD180_CAR, chan); CCRCMD(sc, chan, CCR_ResetChan); } if (!rcnt) ERR(("looses characters during local loopback\n")); /* Now, check data */ for (chan = 0; chan < CD180_NCHAN; chan++) for (i = 0; i < CD180_NFIFO; i++) if (ctest[i] != tchans[chan].rxbuf[i]) ERR(("data mismatch chan %d ptr %d (%d != %d)\n", chan, i, ctest[i], tchans[chan].rxbuf[i])); (void) splx(old_level); return 0; } #ifdef RCDEBUG static void printrcflags(struct rc_chans *rc, char *comment) { struct rc_softc *sc; u_short f = rc->rc_flags; sc = rc->rc_rcb; printf("rc%d/%d: %s flags: %s%s%s%s%s%s%s%s%s%s%s%s\n", rc->rc_rcb->rcb_unit, rc->rc_chan, comment, (f & RC_DTR_OFF)?"DTR_OFF " :"", (f & RC_ACTOUT) ?"ACTOUT " :"", (f & RC_RTSFLOW)?"RTSFLOW " :"", (f & RC_CTSFLOW)?"CTSFLOW " :"", (f & RC_DORXFER)?"DORXFER " :"", (f & RC_DOXXFER)?"DOXXFER " :"", (f & RC_MODCHG) ?"MODCHG " :"", (f & RC_OSUSP) ?"OSUSP " :"", (f & RC_OSBUSY) ?"OSBUSY " :"", (f & RC_WAS_BUFOVFL) ?"BUFOVFL " :"", (f & RC_WAS_SILOVFL) ?"SILOVFL " :"", (f & RC_SEND_RDY) ?"SEND_RDY":""); rcout(sc, CD180_CAR, rc->rc_chan); printf("rc%d/%d: msvr %02x ier %02x ccsr %02x\n", rc->rc_rcb->rcb_unit, rc->rc_chan, rcin(sc, CD180_MSVR), rcin(sc, CD180_IER), rcin(sc, CD180_CCSR)); } #endif /* RCDEBUG */ static void rc_dtrwakeup(void *arg) { struct rc_chans *rc; rc = (struct rc_chans *)arg; rc->rc_flags &= ~RC_DTR_OFF; wakeup(&rc->rc_dtrwait); } static void rc_discard_output(struct rc_chans *rc) { critical_enter(); if (rc->rc_flags & RC_DOXXFER) { rc->rc_rcb->sc_scheduled_event -= LOTS_OF_EVENTS; rc->rc_flags &= ~RC_DOXXFER; } rc->rc_optr = rc->rc_obufend; rc->rc_tp.t_state &= ~TS_BUSY; critical_exit(); ttwwakeup(&rc->rc_tp); } -static void -disc_optim(struct tty *tp, struct termios *t, struct rc_chans *rc) -{ - - if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) - && (!(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; - rc->rc_hotchar = linesw[tp->t_line].l_hotchar; -} - static void rc_wait0(struct rc_softc *sc, int chan, int line) { int rcnt; for (rcnt = 50; rcnt && rcin(sc, CD180_CCR); rcnt--) DELAY(30); if (rcnt == 0) device_printf(sc->sc_dev, "channel %d command timeout, rc.c line: %d\n", chan, line); } static device_method_t rc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rc_probe), DEVMETHOD(device_attach, rc_attach), DEVMETHOD(device_detach, rc_detach), { 0, 0 } }; static driver_t rc_driver = { "rc", rc_methods, sizeof(struct rc_softc), }; DRIVER_MODULE(rc, isa, rc_driver, rc_devclass, 0, 0); diff --git a/sys/dev/rp/rp.c b/sys/dev/rp/rp.c index f99f403709ad..d77c9a11cdca 100644 --- a/sys/dev/rp/rp.c +++ b/sys/dev/rp/rp.c @@ -1,1674 +1,1657 @@ /* * Copyright (c) Comtrol Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted prodived that the follwoing conditions * are met. * 1. Redistributions of source code must retain the above copyright * notive, this list of conditions and the following disclainer. * 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 prodided 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 Comtrol Corporation. * 4. The name of Comtrol Corporation may not be used to endorse or * promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY COMTROL CORPORATION ``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 COMTROL CORPORATION 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, LIFE 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. * */ #include __FBSDID("$FreeBSD$"); /* * rp.c - for RocketPort FreeBSD */ #include "opt_compat.h" #include #include #include #include #include #include #include #include #include #include #include #define ROCKET_C #include #include static const char RocketPortVersion[] = "3.02"; static Byte_t RData[RDATASIZE] = { 0x00, 0x09, 0xf6, 0x82, 0x02, 0x09, 0x86, 0xfb, 0x04, 0x09, 0x00, 0x0a, 0x06, 0x09, 0x01, 0x0a, 0x08, 0x09, 0x8a, 0x13, 0x0a, 0x09, 0xc5, 0x11, 0x0c, 0x09, 0x86, 0x85, 0x0e, 0x09, 0x20, 0x0a, 0x10, 0x09, 0x21, 0x0a, 0x12, 0x09, 0x41, 0xff, 0x14, 0x09, 0x82, 0x00, 0x16, 0x09, 0x82, 0x7b, 0x18, 0x09, 0x8a, 0x7d, 0x1a, 0x09, 0x88, 0x81, 0x1c, 0x09, 0x86, 0x7a, 0x1e, 0x09, 0x84, 0x81, 0x20, 0x09, 0x82, 0x7c, 0x22, 0x09, 0x0a, 0x0a }; static Byte_t RRegData[RREGDATASIZE]= { 0x00, 0x09, 0xf6, 0x82, /* 00: Stop Rx processor */ 0x08, 0x09, 0x8a, 0x13, /* 04: Tx software flow control */ 0x0a, 0x09, 0xc5, 0x11, /* 08: XON char */ 0x0c, 0x09, 0x86, 0x85, /* 0c: XANY */ 0x12, 0x09, 0x41, 0xff, /* 10: Rx mask char */ 0x14, 0x09, 0x82, 0x00, /* 14: Compare/Ignore #0 */ 0x16, 0x09, 0x82, 0x7b, /* 18: Compare #1 */ 0x18, 0x09, 0x8a, 0x7d, /* 1c: Compare #2 */ 0x1a, 0x09, 0x88, 0x81, /* 20: Interrupt #1 */ 0x1c, 0x09, 0x86, 0x7a, /* 24: Ignore/Replace #1 */ 0x1e, 0x09, 0x84, 0x81, /* 28: Interrupt #2 */ 0x20, 0x09, 0x82, 0x7c, /* 2c: Ignore/Replace #2 */ 0x22, 0x09, 0x0a, 0x0a /* 30: Rx FIFO Enable */ }; #if 0 /* IRQ number to MUDBAC register 2 mapping */ Byte_t sIRQMap[16] = { 0,0,0,0x10,0x20,0x30,0,0,0,0x40,0x50,0x60,0x70,0,0,0x80 }; #endif Byte_t rp_sBitMapClrTbl[8] = { 0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f }; Byte_t rp_sBitMapSetTbl[8] = { 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80 }; /* Actually not used */ #if notdef struct termios deftermios = { TTYDEF_IFLAG, TTYDEF_OFLAG, TTYDEF_CFLAG, TTYDEF_LFLAG, { CEOF, CEOL, CEOL, CERASE, CWERASE, CKILL, CREPRINT, _POSIX_VDISABLE, CINTR, CQUIT, CSUSP, CDSUSP, CSTART, CSTOP, CLNEXT, CDISCARD, CMIN, CTIME, CSTATUS, _POSIX_VDISABLE }, TTYDEF_SPEED, TTYDEF_SPEED }; #endif /*************************************************************************** Function: sReadAiopID Purpose: Read the AIOP idenfication number directly from an AIOP. Call: sReadAiopID(CtlP, aiop) CONTROLLER_T *CtlP; Ptr to controller structure int aiop: AIOP index Return: int: Flag AIOPID_XXXX if a valid AIOP is found, where X is replace by an identifying number. Flag AIOPID_NULL if no valid AIOP is found Warnings: No context switches are allowed while executing this function. */ int sReadAiopID(CONTROLLER_T *CtlP, int aiop) { Byte_t AiopID; /* ID byte from AIOP */ rp_writeaiop1(CtlP, aiop, _CMD_REG, RESET_ALL); /* reset AIOP */ rp_writeaiop1(CtlP, aiop, _CMD_REG, 0x0); AiopID = rp_readaiop1(CtlP, aiop, _CHN_STAT0) & 0x07; if(AiopID == 0x06) return(1); else /* AIOP does not exist */ return(-1); } /*************************************************************************** Function: sReadAiopNumChan Purpose: Read the number of channels available in an AIOP directly from an AIOP. Call: sReadAiopNumChan(CtlP, aiop) CONTROLLER_T *CtlP; Ptr to controller structure int aiop: AIOP index Return: int: The number of channels available Comments: The number of channels is determined by write/reads from identical offsets within the SRAM address spaces for channels 0 and 4. If the channel 4 space is mirrored to channel 0 it is a 4 channel AIOP, otherwise it is an 8 channel. Warnings: No context switches are allowed while executing this function. */ int sReadAiopNumChan(CONTROLLER_T *CtlP, int aiop) { Word_t x, y; rp_writeaiop4(CtlP, aiop, _INDX_ADDR,0x12340000L); /* write to chan 0 SRAM */ rp_writeaiop2(CtlP, aiop, _INDX_ADDR,0); /* read from SRAM, chan 0 */ x = rp_readaiop2(CtlP, aiop, _INDX_DATA); rp_writeaiop2(CtlP, aiop, _INDX_ADDR,0x4000); /* read from SRAM, chan 4 */ y = rp_readaiop2(CtlP, aiop, _INDX_DATA); if(x != y) /* if different must be 8 chan */ return(8); else return(4); } /*************************************************************************** Function: sInitChan Purpose: Initialization of a channel and channel structure Call: sInitChan(CtlP,ChP,AiopNum,ChanNum) CONTROLLER_T *CtlP; Ptr to controller structure CHANNEL_T *ChP; Ptr to channel structure int AiopNum; AIOP number within controller int ChanNum; Channel number within AIOP Return: int: TRUE if initialization succeeded, FALSE if it fails because channel number exceeds number of channels available in AIOP. Comments: This function must be called before a channel can be used. Warnings: No range checking on any of the parameters is done. No context switches are allowed while executing this function. */ int sInitChan( CONTROLLER_T *CtlP, CHANNEL_T *ChP, int AiopNum, int ChanNum) { int i, ChOff; Byte_t *ChR; static Byte_t R[4]; if(ChanNum >= CtlP->AiopNumChan[AiopNum]) return(FALSE); /* exceeds num chans in AIOP */ /* Channel, AIOP, and controller identifiers */ ChP->CtlP = CtlP; ChP->ChanID = CtlP->AiopID[AiopNum]; ChP->AiopNum = AiopNum; ChP->ChanNum = ChanNum; /* Initialize the channel from the RData array */ for(i=0; i < RDATASIZE; i+=4) { R[0] = RData[i]; R[1] = RData[i+1] + 0x10 * ChanNum; R[2] = RData[i+2]; R[3] = RData[i+3]; rp_writech4(ChP,_INDX_ADDR,*((DWord_t *)&R[0])); } ChR = ChP->R; for(i=0; i < RREGDATASIZE; i+=4) { ChR[i] = RRegData[i]; ChR[i+1] = RRegData[i+1] + 0x10 * ChanNum; ChR[i+2] = RRegData[i+2]; ChR[i+3] = RRegData[i+3]; } /* Indexed registers */ ChOff = (Word_t)ChanNum * 0x1000; ChP->BaudDiv[0] = (Byte_t)(ChOff + _BAUD); ChP->BaudDiv[1] = (Byte_t)((ChOff + _BAUD) >> 8); ChP->BaudDiv[2] = (Byte_t)BRD9600; ChP->BaudDiv[3] = (Byte_t)(BRD9600 >> 8); rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->BaudDiv[0]); ChP->TxControl[0] = (Byte_t)(ChOff + _TX_CTRL); ChP->TxControl[1] = (Byte_t)((ChOff + _TX_CTRL) >> 8); ChP->TxControl[2] = 0; ChP->TxControl[3] = 0; rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->TxControl[0]); ChP->RxControl[0] = (Byte_t)(ChOff + _RX_CTRL); ChP->RxControl[1] = (Byte_t)((ChOff + _RX_CTRL) >> 8); ChP->RxControl[2] = 0; ChP->RxControl[3] = 0; rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->RxControl[0]); ChP->TxEnables[0] = (Byte_t)(ChOff + _TX_ENBLS); ChP->TxEnables[1] = (Byte_t)((ChOff + _TX_ENBLS) >> 8); ChP->TxEnables[2] = 0; ChP->TxEnables[3] = 0; rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->TxEnables[0]); ChP->TxCompare[0] = (Byte_t)(ChOff + _TXCMP1); ChP->TxCompare[1] = (Byte_t)((ChOff + _TXCMP1) >> 8); ChP->TxCompare[2] = 0; ChP->TxCompare[3] = 0; rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->TxCompare[0]); ChP->TxReplace1[0] = (Byte_t)(ChOff + _TXREP1B1); ChP->TxReplace1[1] = (Byte_t)((ChOff + _TXREP1B1) >> 8); ChP->TxReplace1[2] = 0; ChP->TxReplace1[3] = 0; rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->TxReplace1[0]); ChP->TxReplace2[0] = (Byte_t)(ChOff + _TXREP2); ChP->TxReplace2[1] = (Byte_t)((ChOff + _TXREP2) >> 8); ChP->TxReplace2[2] = 0; ChP->TxReplace2[3] = 0; rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->TxReplace2[0]); ChP->TxFIFOPtrs = ChOff + _TXF_OUTP; ChP->TxFIFO = ChOff + _TX_FIFO; rp_writech1(ChP,_CMD_REG,(Byte_t)ChanNum | RESTXFCNT); /* apply reset Tx FIFO count */ rp_writech1(ChP,_CMD_REG,(Byte_t)ChanNum); /* remove reset Tx FIFO count */ rp_writech2(ChP,_INDX_ADDR,ChP->TxFIFOPtrs); /* clear Tx in/out ptrs */ rp_writech2(ChP,_INDX_DATA,0); ChP->RxFIFOPtrs = ChOff + _RXF_OUTP; ChP->RxFIFO = ChOff + _RX_FIFO; rp_writech1(ChP,_CMD_REG,(Byte_t)ChanNum | RESRXFCNT); /* apply reset Rx FIFO count */ rp_writech1(ChP,_CMD_REG,(Byte_t)ChanNum); /* remove reset Rx FIFO count */ rp_writech2(ChP,_INDX_ADDR,ChP->RxFIFOPtrs); /* clear Rx out ptr */ rp_writech2(ChP,_INDX_DATA,0); rp_writech2(ChP,_INDX_ADDR,ChP->RxFIFOPtrs + 2); /* clear Rx in ptr */ rp_writech2(ChP,_INDX_DATA,0); ChP->TxPrioCnt = ChOff + _TXP_CNT; rp_writech2(ChP,_INDX_ADDR,ChP->TxPrioCnt); rp_writech1(ChP,_INDX_DATA,0); ChP->TxPrioPtr = ChOff + _TXP_PNTR; rp_writech2(ChP,_INDX_ADDR,ChP->TxPrioPtr); rp_writech1(ChP,_INDX_DATA,0); ChP->TxPrioBuf = ChOff + _TXP_BUF; sEnRxProcessor(ChP); /* start the Rx processor */ return(TRUE); } /*************************************************************************** Function: sStopRxProcessor Purpose: Stop the receive processor from processing a channel. Call: sStopRxProcessor(ChP) CHANNEL_T *ChP; Ptr to channel structure Comments: The receive processor can be started again with sStartRxProcessor(). This function causes the receive processor to skip over the stopped channel. It does not stop it from processing other channels. Warnings: No context switches are allowed while executing this function. Do not leave the receive processor stopped for more than one character time. After calling this function a delay of 4 uS is required to ensure that the receive processor is no longer processing this channel. */ void sStopRxProcessor(CHANNEL_T *ChP) { Byte_t R[4]; R[0] = ChP->R[0]; R[1] = ChP->R[1]; R[2] = 0x0a; R[3] = ChP->R[3]; rp_writech4(ChP, _INDX_ADDR,*(DWord_t *)&R[0]); } /*************************************************************************** Function: sFlushRxFIFO Purpose: Flush the Rx FIFO Call: sFlushRxFIFO(ChP) CHANNEL_T *ChP; Ptr to channel structure Return: void Comments: To prevent data from being enqueued or dequeued in the Tx FIFO while it is being flushed the receive processor is stopped and the transmitter is disabled. After these operations a 4 uS delay is done before clearing the pointers to allow the receive processor to stop. These items are handled inside this function. Warnings: No context switches are allowed while executing this function. */ void sFlushRxFIFO(CHANNEL_T *ChP) { int i; Byte_t Ch; /* channel number within AIOP */ int RxFIFOEnabled; /* TRUE if Rx FIFO enabled */ if(sGetRxCnt(ChP) == 0) /* Rx FIFO empty */ return; /* don't need to flush */ RxFIFOEnabled = FALSE; if(ChP->R[0x32] == 0x08) /* Rx FIFO is enabled */ { RxFIFOEnabled = TRUE; sDisRxFIFO(ChP); /* disable it */ for(i=0; i < 2000/200; i++) /* delay 2 uS to allow proc to disable FIFO*/ rp_readch1(ChP,_INT_CHAN); /* depends on bus i/o timing */ } sGetChanStatus(ChP); /* clear any pending Rx errors in chan stat */ Ch = (Byte_t)sGetChanNum(ChP); rp_writech1(ChP,_CMD_REG,Ch | RESRXFCNT); /* apply reset Rx FIFO count */ rp_writech1(ChP,_CMD_REG,Ch); /* remove reset Rx FIFO count */ rp_writech2(ChP,_INDX_ADDR,ChP->RxFIFOPtrs); /* clear Rx out ptr */ rp_writech2(ChP,_INDX_DATA,0); rp_writech2(ChP,_INDX_ADDR,ChP->RxFIFOPtrs + 2); /* clear Rx in ptr */ rp_writech2(ChP,_INDX_DATA,0); if(RxFIFOEnabled) sEnRxFIFO(ChP); /* enable Rx FIFO */ } /*************************************************************************** Function: sFlushTxFIFO Purpose: Flush the Tx FIFO Call: sFlushTxFIFO(ChP) CHANNEL_T *ChP; Ptr to channel structure Return: void Comments: To prevent data from being enqueued or dequeued in the Tx FIFO while it is being flushed the receive processor is stopped and the transmitter is disabled. After these operations a 4 uS delay is done before clearing the pointers to allow the receive processor to stop. These items are handled inside this function. Warnings: No context switches are allowed while executing this function. */ void sFlushTxFIFO(CHANNEL_T *ChP) { int i; Byte_t Ch; /* channel number within AIOP */ int TxEnabled; /* TRUE if transmitter enabled */ if(sGetTxCnt(ChP) == 0) /* Tx FIFO empty */ return; /* don't need to flush */ TxEnabled = FALSE; if(ChP->TxControl[3] & TX_ENABLE) { TxEnabled = TRUE; sDisTransmit(ChP); /* disable transmitter */ } sStopRxProcessor(ChP); /* stop Rx processor */ for(i = 0; i < 4000/200; i++) /* delay 4 uS to allow proc to stop */ rp_readch1(ChP,_INT_CHAN); /* depends on bus i/o timing */ Ch = (Byte_t)sGetChanNum(ChP); rp_writech1(ChP,_CMD_REG,Ch | RESTXFCNT); /* apply reset Tx FIFO count */ rp_writech1(ChP,_CMD_REG,Ch); /* remove reset Tx FIFO count */ rp_writech2(ChP,_INDX_ADDR,ChP->TxFIFOPtrs); /* clear Tx in/out ptrs */ rp_writech2(ChP,_INDX_DATA,0); if(TxEnabled) sEnTransmit(ChP); /* enable transmitter */ sStartRxProcessor(ChP); /* restart Rx processor */ } /*************************************************************************** Function: sWriteTxPrioByte Purpose: Write a byte of priority transmit data to a channel Call: sWriteTxPrioByte(ChP,Data) CHANNEL_T *ChP; Ptr to channel structure Byte_t Data; The transmit data byte Return: int: 1 if the bytes is successfully written, otherwise 0. Comments: The priority byte is transmitted before any data in the Tx FIFO. Warnings: No context switches are allowed while executing this function. */ int sWriteTxPrioByte(CHANNEL_T *ChP, Byte_t Data) { Byte_t DWBuf[4]; /* buffer for double word writes */ Word_t *WordPtr; /* must be far because Win SS != DS */ if(sGetTxCnt(ChP) > 1) /* write it to Tx priority buffer */ { rp_writech2(ChP,_INDX_ADDR,ChP->TxPrioCnt); /* get priority buffer status */ if(rp_readch1(ChP,_INDX_DATA) & PRI_PEND) /* priority buffer busy */ return(0); /* nothing sent */ WordPtr = (Word_t *)(&DWBuf[0]); *WordPtr = ChP->TxPrioBuf; /* data byte address */ DWBuf[2] = Data; /* data byte value */ rp_writech4(ChP,_INDX_ADDR,*((DWord_t *)(&DWBuf[0]))); /* write it out */ *WordPtr = ChP->TxPrioCnt; /* Tx priority count address */ DWBuf[2] = PRI_PEND + 1; /* indicate 1 byte pending */ DWBuf[3] = 0; /* priority buffer pointer */ rp_writech4(ChP,_INDX_ADDR,*((DWord_t *)(&DWBuf[0]))); /* write it out */ } else /* write it to Tx FIFO */ { sWriteTxByte(ChP,sGetTxRxDataIO(ChP),Data); } return(1); /* 1 byte sent */ } /*************************************************************************** Function: sEnInterrupts Purpose: Enable one or more interrupts for a channel Call: sEnInterrupts(ChP,Flags) CHANNEL_T *ChP; Ptr to channel structure Word_t Flags: Interrupt enable flags, can be any combination of the following flags: TXINT_EN: Interrupt on Tx FIFO empty RXINT_EN: Interrupt on Rx FIFO at trigger level (see sSetRxTrigger()) SRCINT_EN: Interrupt on SRC (Special Rx Condition) MCINT_EN: Interrupt on modem input change CHANINT_EN: Allow channel interrupt signal to the AIOP's Interrupt Channel Register. Return: void Comments: If an interrupt enable flag is set in Flags, that interrupt will be enabled. If an interrupt enable flag is not set in Flags, that interrupt will not be changed. Interrupts can be disabled with function sDisInterrupts(). This function sets the appropriate bit for the channel in the AIOP's Interrupt Mask Register if the CHANINT_EN flag is set. This allows this channel's bit to be set in the AIOP's Interrupt Channel Register. Interrupts must also be globally enabled before channel interrupts will be passed on to the host. This is done with function sEnGlobalInt(). In some cases it may be desirable to disable interrupts globally but enable channel interrupts. This would allow the global interrupt status register to be used to determine which AIOPs need service. */ void sEnInterrupts(CHANNEL_T *ChP,Word_t Flags) { Byte_t Mask; /* Interrupt Mask Register */ ChP->RxControl[2] |= ((Byte_t)Flags & (RXINT_EN | SRCINT_EN | MCINT_EN)); rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->RxControl[0]); ChP->TxControl[2] |= ((Byte_t)Flags & TXINT_EN); rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->TxControl[0]); if(Flags & CHANINT_EN) { Mask = rp_readch1(ChP,_INT_MASK) | rp_sBitMapSetTbl[ChP->ChanNum]; rp_writech1(ChP,_INT_MASK,Mask); } } /*************************************************************************** Function: sDisInterrupts Purpose: Disable one or more interrupts for a channel Call: sDisInterrupts(ChP,Flags) CHANNEL_T *ChP; Ptr to channel structure Word_t Flags: Interrupt flags, can be any combination of the following flags: TXINT_EN: Interrupt on Tx FIFO empty RXINT_EN: Interrupt on Rx FIFO at trigger level (see sSetRxTrigger()) SRCINT_EN: Interrupt on SRC (Special Rx Condition) MCINT_EN: Interrupt on modem input change CHANINT_EN: Disable channel interrupt signal to the AIOP's Interrupt Channel Register. Return: void Comments: If an interrupt flag is set in Flags, that interrupt will be disabled. If an interrupt flag is not set in Flags, that interrupt will not be changed. Interrupts can be enabled with function sEnInterrupts(). This function clears the appropriate bit for the channel in the AIOP's Interrupt Mask Register if the CHANINT_EN flag is set. This blocks this channel's bit from being set in the AIOP's Interrupt Channel Register. */ void sDisInterrupts(CHANNEL_T *ChP,Word_t Flags) { Byte_t Mask; /* Interrupt Mask Register */ ChP->RxControl[2] &= ~((Byte_t)Flags & (RXINT_EN | SRCINT_EN | MCINT_EN)); rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->RxControl[0]); ChP->TxControl[2] &= ~((Byte_t)Flags & TXINT_EN); rp_writech4(ChP,_INDX_ADDR,*(DWord_t *)&ChP->TxControl[0]); if(Flags & CHANINT_EN) { Mask = rp_readch1(ChP,_INT_MASK) & rp_sBitMapClrTbl[ChP->ChanNum]; rp_writech1(ChP,_INT_MASK,Mask); } } /********************************************************************* Begin FreeBsd-specific driver code **********************************************************************/ static timeout_t rpdtrwakeup; static d_open_t rpopen; static d_close_t rpclose; static d_write_t rpwrite; static d_ioctl_t rpioctl; struct cdevsw rp_cdevsw = { .d_version = D_VERSION, .d_open = rpopen, .d_close = rpclose, .d_write = rpwrite, .d_ioctl = rpioctl, .d_name = "rp", .d_flags = D_TTY | D_NEEDGIANT, }; static int rp_num_ports_open = 0; static int rp_ndevs = 0; static int minor_to_unit[128]; static int rp_num_ports[4]; /* Number of ports on each controller */ #define POLL_INTERVAL 1 #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_UNIT(dev) (MINOR_TO_UNIT(minor(dev)) #define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) #define MINOR_MAGIC(dev) ((minor(dev)) & ~MINOR_MAGIC_MASK) #define IS_CALLOUT(dev) (minor(dev) & CALLOUT_MASK) #define IS_CONTROL(dev) (minor(dev) & CONTROL_MASK) #define RP_ISMULTIPORT(dev) ((dev)->id_flags & 0x1) #define RP_MPMASTER(dev) (((dev)->id_flags >> 8) & 0xff) #define RP_NOTAST4(dev) ((dev)->id_flags & 0x04) static struct rp_port *p_rp_addr[4]; static struct rp_port *p_rp_table[MAX_RP_PORTS]; #define rp_addr(unit) (p_rp_addr[unit]) #define rp_table(port) (p_rp_table[port]) /* * The top-level routines begin here */ static int rpparam(struct tty *, struct termios *); static void rpstart(struct tty *); static void rpstop(struct tty *, int); static void rphardclose (struct rp_port *); -static void rp_disc_optim (struct tty *tp, struct termios *t); static void rp_do_receive(struct rp_port *rp, struct tty *tp, CHANNEL_t *cp, unsigned int ChanStatus) { int spl; unsigned int CharNStat; int ToRecv, wRecv, ch, ttynocopy; ToRecv = sGetRxCnt(cp); if(ToRecv == 0) return; /* If status indicates there are errored characters in the FIFO, then enter status mode (a word in FIFO holds characters and status) */ if(ChanStatus & (RXFOVERFL | RXBREAK | RXFRAME | RXPARITY)) { if(!(ChanStatus & STATMODE)) { ChanStatus |= STATMODE; sEnRxStatusMode(cp); } } /* if we previously entered status mode then read down the FIFO one word at a time, pulling apart the character and the status. Update error counters depending on status. */ if(ChanStatus & STATMODE) { while(ToRecv) { if(tp->t_state & TS_TBLOCK) { break; } CharNStat = rp_readch2(cp,sGetTxRxDataIO(cp)); ch = CharNStat & 0xff; if((CharNStat & STMBREAK) || (CharNStat & STMFRAMEH)) ch |= TTY_FE; else if (CharNStat & STMPARITYH) ch |= TTY_PE; else if (CharNStat & STMRCVROVRH) rp->rp_overflows++; ttyld_rint(tp, ch); ToRecv--; } /* After emtying FIFO in status mode, turn off status mode */ if(sGetRxCnt(cp) == 0) { sDisRxStatusMode(cp); } } else { /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ ToRecv = sGetRxCnt(cp); if ( tp->t_state & TS_CAN_BYPASS_L_RINT ) { if ( ToRecv > RXFIFO_SIZE ) { ToRecv = RXFIFO_SIZE; } wRecv = ToRecv >> 1; if ( wRecv ) { rp_readmultich2(cp,sGetTxRxDataIO(cp),(u_int16_t *)rp->RxBuf,wRecv); } if ( ToRecv & 1 ) { ((unsigned char *)rp->RxBuf)[(ToRecv-1)] = (u_char) rp_readch1(cp,sGetTxRxDataIO(cp)); } tk_nin += ToRecv; tk_rawcc += ToRecv; tp->t_rawcc += ToRecv; ttynocopy = b_to_q((char *)rp->RxBuf, ToRecv, &tp->t_rawq); ttwakeup(tp); } else { while (ToRecv) { if(tp->t_state & TS_TBLOCK) { break; } ch = (u_char) rp_readch1(cp,sGetTxRxDataIO(cp)); spl = spltty(); ttyld_rint(tp, ch); splx(spl); ToRecv--; } } } } static void rp_handle_port(struct rp_port *rp) { CHANNEL_t *cp; struct tty *tp; unsigned int IntMask, ChanStatus; if(!rp) return; cp = &rp->rp_channel; tp = rp->rp_tty; IntMask = sGetChanIntID(cp); IntMask = IntMask & rp->rp_intmask; ChanStatus = sGetChanStatus(cp); if(IntMask & RXF_TRIG) if(!(tp->t_state & TS_TBLOCK) && (tp->t_state & TS_CARR_ON) && (tp->t_state & TS_ISOPEN)) { rp_do_receive(rp, tp, cp, ChanStatus); } if(IntMask & DELTA_CD) { if(ChanStatus & CD_ACT) { if(!(tp->t_state & TS_CARR_ON) ) { (void)ttyld_modem(tp, 1); } } else { if((tp->t_state & TS_CARR_ON)) { (void)ttyld_modem(tp, 0); if(ttyld_modem(tp, 0) == 0) { rphardclose(rp); } } } } /* oldcts = rp->rp_cts; rp->rp_cts = ((ChanStatus & CTS_ACT) != 0); if(oldcts != rp->rp_cts) { printf("CTS change (now %s)... on port %d\n", rp->rp_cts ? "on" : "off", rp->rp_port); } */ } static void rp_do_poll(void *not_used) { CONTROLLER_t *ctl; struct rp_port *rp; struct tty *tp; int unit, aiop, ch, line, count; unsigned char CtlMask, AiopMask; for(unit = 0; unit < rp_ndevs; unit++) { rp = rp_addr(unit); ctl = rp->rp_ctlp; CtlMask = ctl->ctlmask(ctl); for(aiop=0; CtlMask; CtlMask >>=1, aiop++) { if(CtlMask & 1) { AiopMask = sGetAiopIntStatus(ctl, aiop); for(ch = 0; AiopMask; AiopMask >>=1, ch++) { if(AiopMask & 1) { line = (unit << 5) | (aiop << 3) | ch; rp = rp_table(line); rp_handle_port(rp); } } } } for(line = 0, rp = rp_addr(unit); line < rp_num_ports[unit]; line++, rp++) { tp = rp->rp_tty; if((tp->t_state & TS_BUSY) && (tp->t_state & TS_ISOPEN)) { count = sGetTxCnt(&rp->rp_channel); if(count == 0) tp->t_state &= ~(TS_BUSY); if(!(tp->t_state & TS_TTSTOP) && (count <= rp->rp_restart)) { ttyld_start(tp); } } } } if(rp_num_ports_open) timeout(rp_do_poll, (void *)NULL, POLL_INTERVAL); } int rp_attachcommon(CONTROLLER_T *ctlp, int num_aiops, int num_ports) { int oldspl, unit; int num_chan; int aiop, chan, port; int ChanStatus, line, i, count; int retval; struct rp_port *rp; struct tty *tty; dev_t *dev_nodes; unit = device_get_unit(ctlp->dev); printf("RocketPort%d (Version %s) %d ports.\n", unit, RocketPortVersion, num_ports); rp_num_ports[unit] = num_ports; ctlp->rp = rp = (struct rp_port *) malloc(sizeof(struct rp_port) * num_ports, M_TTYS, M_NOWAIT); if (rp == NULL) { device_printf(ctlp->dev, "rp_attachcommon: Could not malloc rp_ports structures.\n"); retval = ENOMEM; goto nogo; } count = unit * 32; /* board times max ports per card SG */ for(i=count;i < (count + rp_num_ports[unit]);i++) minor_to_unit[i] = unit; bzero(rp, sizeof(struct rp_port) * num_ports); ctlp->tty = tty = (struct tty *) malloc(sizeof(struct tty) * num_ports, M_TTYS, M_NOWAIT | M_ZERO); if(tty == NULL) { device_printf(ctlp->dev, "rp_attachcommon: Could not malloc tty structures.\n"); retval = ENOMEM; goto nogo; } oldspl = spltty(); rp_addr(unit) = rp; splx(oldspl); dev_nodes = ctlp->dev_nodes = malloc(sizeof(*(ctlp->dev_nodes)) * rp_num_ports[unit] * 6, M_DEVBUF, M_NOWAIT | M_ZERO); if(ctlp->dev_nodes == NULL) { device_printf(ctlp->dev, "rp_attachcommon: Could not malloc device node structures.\n"); retval = ENOMEM; goto nogo; } for (i = 0 ; i < rp_num_ports[unit] ; i++) { *(dev_nodes++) = make_dev(&rp_cdevsw, ((unit + 1) << 16) | i, UID_ROOT, GID_WHEEL, 0666, "ttyR%c", i <= 9 ? '0' + i : 'a' + i - 10); *(dev_nodes++) = make_dev(&rp_cdevsw, ((unit + 1) << 16) | i | 0x20, UID_ROOT, GID_WHEEL, 0666, "ttyiR%c", i <= 9 ? '0' + i : 'a' + i - 10); *(dev_nodes++) = make_dev(&rp_cdevsw, ((unit + 1) << 16) | i | 0x40, UID_ROOT, GID_WHEEL, 0666, "ttylR%c", i <= 9 ? '0' + i : 'a' + i - 10); *(dev_nodes++) = make_dev(&rp_cdevsw, ((unit + 1) << 16) | i | 0x80, UID_ROOT, GID_WHEEL, 0666, "cuaR%c", i <= 9 ? '0' + i : 'a' + i - 10); *(dev_nodes++) = make_dev(&rp_cdevsw, ((unit + 1) << 16) | i | 0xa0, UID_ROOT, GID_WHEEL, 0666, "cuaiR%c", i <= 9 ? '0' + i : 'a' + i - 10); *(dev_nodes++) = make_dev(&rp_cdevsw, ((unit + 1) << 16) | i | 0xc0, UID_ROOT, GID_WHEEL, 0666, "cualR%c", i <= 9 ? '0' + i : 'a' + i - 10); } port = 0; for(aiop=0; aiop < num_aiops; aiop++) { num_chan = sGetAiopNumChan(ctlp, aiop); for(chan=0; chan < num_chan; chan++, port++, rp++, tty++) { rp->rp_tty = tty; rp->rp_port = port; rp->rp_ctlp = ctlp; rp->rp_unit = unit; rp->rp_chan = chan; rp->rp_aiop = aiop; tty->t_line = 0; /* tty->t_termios = deftermios; */ rp->dtr_wait = 3 * hz; rp->it_in.c_iflag = 0; rp->it_in.c_oflag = 0; rp->it_in.c_cflag = TTYDEF_CFLAG; rp->it_in.c_lflag = 0; termioschars(&rp->it_in); /* termioschars(&tty->t_termios); */ rp->it_in.c_ispeed = rp->it_in.c_ospeed = TTYDEF_SPEED; rp->it_out = rp->it_in; rp->rp_intmask = RXF_TRIG | TXFIFO_MT | SRC_INT | DELTA_CD | DELTA_CTS | DELTA_DSR; #if notdef ChanStatus = sGetChanStatus(&rp->rp_channel); #endif /* notdef */ if(sInitChan(ctlp, &rp->rp_channel, aiop, chan) == 0) { device_printf(ctlp->dev, "RocketPort sInitChan(%d, %d, %d) failed.\n", unit, aiop, chan); retval = ENXIO; goto nogo; } ChanStatus = sGetChanStatus(&rp->rp_channel); rp->rp_cts = (ChanStatus & CTS_ACT) != 0; line = (unit << 5) | (aiop << 3) | chan; rp_table(line) = rp; } } rp_ndevs++; return (0); nogo: rp_releaseresource(ctlp); return (retval); } void rp_releaseresource(CONTROLLER_t *ctlp) { int i, s, unit; unit = device_get_unit(ctlp->dev); if (ctlp->rp != NULL) { s = spltty(); for (i = 0 ; i < sizeof(p_rp_addr) / sizeof(*p_rp_addr) ; i++) if (p_rp_addr[i] == ctlp->rp) p_rp_addr[i] = NULL; for (i = 0 ; i < sizeof(p_rp_table) / sizeof(*p_rp_table) ; i++) if (p_rp_table[i] == ctlp->rp) p_rp_table[i] = NULL; splx(s); free(ctlp->rp, M_DEVBUF); ctlp->rp = NULL; } if (ctlp->tty != NULL) { free(ctlp->tty, M_DEVBUF); ctlp->tty = NULL; } if (ctlp->dev != NULL) { for (i = 0 ; i < rp_num_ports[unit] * 6 ; i++) destroy_dev(ctlp->dev_nodes[i]); free(ctlp->dev_nodes, M_DEVBUF); ctlp->dev = NULL; } } static int rpopen(dev, flag, mode, td) dev_t dev; int flag, mode; struct thread *td; { struct rp_port *rp; int unit, port, mynor, umynor, flags; /* SG */ struct tty *tp; int oldspl, error; unsigned int IntMask, ChanStatus; umynor = (((minor(dev) >> 16) -1) * 32); /* SG */ port = (minor(dev) & 0x1f); /* SG */ mynor = (port + umynor); /* SG */ unit = minor_to_unit[mynor]; if (rp_addr(unit) == NULL) return (ENXIO); if(IS_CONTROL(dev)) return(0); rp = rp_addr(unit) + port; /* rp->rp_tty = &rp_tty[rp->rp_port]; */ tp = rp->rp_tty; dev->si_tty = tp; oldspl = spltty(); open_top: while(rp->state & ~SET_DTR) { error = tsleep(&rp->dtr_wait, TTIPRI | PCATCH, "rpdtr", 0); if(error != 0) goto out; } if(tp->t_state & TS_ISOPEN) { if(IS_CALLOUT(dev)) { if(!rp->active_out) { error = EBUSY; goto out; } } else { if(rp->active_out) { if(flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&rp->active_out, TTIPRI | PCATCH, "rpbi", 0); if(error != 0) goto out; goto open_top; } } if(tp->t_state & TS_XCLUDE && suser(td) != 0) { splx(oldspl); error = EBUSY; goto out2; } } else { tp->t_dev = dev; tp->t_param = rpparam; tp->t_oproc = rpstart; tp->t_stop = rpstop; tp->t_line = 0; tp->t_termios = IS_CALLOUT(dev) ? rp->it_out : rp->it_in; tp->t_ififosize = 512; tp->t_ispeedwat = (speed_t)-1; tp->t_ospeedwat = (speed_t)-1; flags = 0; flags |= SET_RTS; flags |= SET_DTR; rp->rp_channel.TxControl[3] = ((rp->rp_channel.TxControl[3] & ~(SET_RTS | SET_DTR)) | flags); rp_writech4(&rp->rp_channel,_INDX_ADDR, *(DWord_t *) &(rp->rp_channel.TxControl[0])); sSetRxTrigger(&rp->rp_channel, TRIG_1); sDisRxStatusMode(&rp->rp_channel); sFlushRxFIFO(&rp->rp_channel); sFlushTxFIFO(&rp->rp_channel); sEnInterrupts(&rp->rp_channel, (TXINT_EN|MCINT_EN|RXINT_EN|SRCINT_EN|CHANINT_EN)); sSetRxTrigger(&rp->rp_channel, TRIG_1); sDisRxStatusMode(&rp->rp_channel); sClrTxXOFF(&rp->rp_channel); /* sDisRTSFlowCtl(&rp->rp_channel); sDisCTSFlowCtl(&rp->rp_channel); */ sDisTxSoftFlowCtl(&rp->rp_channel); sStartRxProcessor(&rp->rp_channel); sEnRxFIFO(&rp->rp_channel); sEnTransmit(&rp->rp_channel); /* sSetDTR(&rp->rp_channel); sSetRTS(&rp->rp_channel); */ ++rp->wopeners; error = rpparam(tp, &tp->t_termios); --rp->wopeners; if(error != 0) { splx(oldspl); return(error); } rp_num_ports_open++; IntMask = sGetChanIntID(&rp->rp_channel); IntMask = IntMask & rp->rp_intmask; ChanStatus = sGetChanStatus(&rp->rp_channel); if((IntMask & DELTA_CD) || IS_CALLOUT(dev)) { if((ChanStatus & CD_ACT) || IS_CALLOUT(dev)) { (void)ttyld_modem(tp, 1); } } if(rp_num_ports_open == 1) timeout(rp_do_poll, (void *)NULL, POLL_INTERVAL); } if(!(flag&O_NONBLOCK) && !(tp->t_cflag&CLOCAL) && !(tp->t_state & TS_CARR_ON) && !(IS_CALLOUT(dev))) { ++rp->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "rpdcd", 0); --rp->wopeners; if(error != 0) goto out; goto open_top; } error = ttyld_open(tp, dev); - rp_disc_optim(tp, &tp->t_termios); + ttyldoptim(tp); if(tp->t_state & TS_ISOPEN && IS_CALLOUT(dev)) rp->active_out = TRUE; /* if(rp_num_ports_open == 1) timeout(rp_do_poll, (void *)NULL, POLL_INTERVAL); */ out: splx(oldspl); if(!(tp->t_state & TS_ISOPEN) && rp->wopeners == 0) { rphardclose(rp); } out2: if (error == 0) device_busy(rp->rp_ctlp->dev); return(error); } static int rpclose(dev, flag, mode, td) dev_t dev; int flag, mode; struct thread *td; { int oldspl, unit, mynor, umynor, port; /* SG */ struct rp_port *rp; struct tty *tp; CHANNEL_t *cp; umynor = (((minor(dev) >> 16) -1) * 32); /* SG */ port = (minor(dev) & 0x1f); /* SG */ mynor = (port + umynor); /* SG */ unit = minor_to_unit[mynor]; /* SG */ if(IS_CONTROL(dev)) return(0); rp = rp_addr(unit) + port; cp = &rp->rp_channel; tp = rp->rp_tty; oldspl = spltty(); ttyld_close(tp, flag); - rp_disc_optim(tp, &tp->t_termios); + ttyldoptim(tp); rphardclose(rp); tp->t_state &= ~TS_BUSY; ttyclose(tp); splx(oldspl); device_unbusy(rp->rp_ctlp->dev); return(0); } static void rphardclose(struct rp_port *rp) { int mynor; struct tty *tp; CHANNEL_t *cp; cp = &rp->rp_channel; tp = rp->rp_tty; mynor = MINOR_MAGIC(tp->t_dev); sFlushRxFIFO(cp); sFlushTxFIFO(cp); sDisTransmit(cp); sDisInterrupts(cp, TXINT_EN|MCINT_EN|RXINT_EN|SRCINT_EN|CHANINT_EN); sDisRTSFlowCtl(cp); sDisCTSFlowCtl(cp); sDisTxSoftFlowCtl(cp); sClrTxXOFF(cp); if(tp->t_cflag&HUPCL || !(tp->t_state&TS_ISOPEN) || !rp->active_out) { sClrDTR(cp); } if(IS_CALLOUT(tp->t_dev)) { sClrDTR(cp); } if(rp->dtr_wait != 0) { timeout(rpdtrwakeup, rp, rp->dtr_wait); rp->state |= ~SET_DTR; } rp->active_out = FALSE; wakeup(&rp->active_out); wakeup(TSA_CARR_ON(tp)); } static int rpwrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { struct rp_port *rp; struct tty *tp; int unit, mynor, port, umynor, error = 0; /* SG */ umynor = (((minor(dev) >> 16) -1) * 32); /* SG */ port = (minor(dev) & 0x1f); /* SG */ mynor = (port + umynor); /* SG */ unit = minor_to_unit[mynor]; /* SG */ if(IS_CONTROL(dev)) return(ENODEV); rp = rp_addr(unit) + port; tp = rp->rp_tty; while(rp->rp_disable_writes) { rp->rp_waiting = 1; error = ttysleep(tp, (caddr_t)rp, TTOPRI|PCATCH, "rp_write", 0); if (error) return(error); } error = ttyld_write(tp, uio, flag); return error; } static void rpdtrwakeup(void *chan) { struct rp_port *rp; rp = (struct rp_port *)chan; rp->state &= SET_DTR; wakeup(&rp->dtr_wait); } static int rpioctl(dev, cmd, data, flag, td) dev_t dev; u_long cmd; caddr_t data; int flag; struct thread *td; { struct rp_port *rp; CHANNEL_t *cp; struct tty *tp; int unit, mynor, port, umynor; /* SG */ int oldspl; int error = 0; int arg, flags, result, ChanStatus; struct termios *t; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) u_long oldcmd; struct termios term; #endif umynor = (((minor(dev) >> 16) -1) * 32); /* SG */ port = (minor(dev) & 0x1f); /* SG */ mynor = (port + umynor); /* SG */ unit = minor_to_unit[mynor]; rp = rp_addr(unit) + port; if(IS_CONTROL(dev)) { struct termios *ct; switch (IS_CONTROL(dev)) { case CONTROL_INIT_STATE: ct = IS_CALLOUT(dev) ? &rp->it_out : &rp->it_in; break; case CONTROL_LOCK_STATE: ct = IS_CALLOUT(dev) ? &rp->lt_out : &rp->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); default: return(ENOTTY); } } tp = rp->rp_tty; cp = &rp->rp_channel; #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 *)data; struct termios *lt = IS_CALLOUT(dev) ? &rp->lt_out : &rp->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); 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; } t = &tp->t_termios; error = ttyioctl(dev, cmd, data, flag, td); - rp_disc_optim(tp, &tp->t_termios); + ttyldoptim(tp); if(error != ENOTTY) return(error); oldspl = spltty(); flags = rp->rp_channel.TxControl[3]; switch(cmd) { case TIOCSBRK: sSendBreak(&rp->rp_channel); break; case TIOCCBRK: sClrBreak(&rp->rp_channel); break; case TIOCSDTR: sSetDTR(&rp->rp_channel); sSetRTS(&rp->rp_channel); break; case TIOCCDTR: sClrDTR(&rp->rp_channel); break; case TIOCMSET: arg = *(int *) data; flags = 0; if(arg & TIOCM_RTS) flags |= SET_RTS; if(arg & TIOCM_DTR) flags |= SET_DTR; rp->rp_channel.TxControl[3] = ((rp->rp_channel.TxControl[3] & ~(SET_RTS | SET_DTR)) | flags); rp_writech4(&rp->rp_channel,_INDX_ADDR, *(DWord_t *) &(rp->rp_channel.TxControl[0])); break; case TIOCMBIS: arg = *(int *) data; flags = 0; if(arg & TIOCM_RTS) flags |= SET_RTS; if(arg & TIOCM_DTR) flags |= SET_DTR; rp->rp_channel.TxControl[3] |= flags; rp_writech4(&rp->rp_channel,_INDX_ADDR, *(DWord_t *) &(rp->rp_channel.TxControl[0])); break; case TIOCMBIC: arg = *(int *) data; flags = 0; if(arg & TIOCM_RTS) flags |= SET_RTS; if(arg & TIOCM_DTR) flags |= SET_DTR; rp->rp_channel.TxControl[3] &= ~flags; rp_writech4(&rp->rp_channel,_INDX_ADDR, *(DWord_t *) &(rp->rp_channel.TxControl[0])); break; case TIOCMGET: ChanStatus = sGetChanStatusLo(&rp->rp_channel); flags = rp->rp_channel.TxControl[3]; result = TIOCM_LE; /* always on while open for some reason */ result |= (((flags & SET_DTR) ? TIOCM_DTR : 0) | ((flags & SET_RTS) ? TIOCM_RTS : 0) | ((ChanStatus & CD_ACT) ? TIOCM_CAR : 0) | ((ChanStatus & DSR_ACT) ? TIOCM_DSR : 0) | ((ChanStatus & CTS_ACT) ? TIOCM_CTS : 0)); if(rp->rp_channel.RxControl[2] & RTSFC_EN) { result |= TIOCM_RTS; } *(int *)data = result; break; case TIOCMSDTRWAIT: error = suser(td); if(error != 0) { splx(oldspl); return(error); } rp->dtr_wait = *(int *)data * hz/100; break; case TIOCMGDTRWAIT: *(int *)data = rp->dtr_wait * 100/hz; break; default: splx(oldspl); return ENOTTY; } splx(oldspl); return(0); } static struct speedtab baud_table[] = { {B0, 0}, {B50, BRD50}, {B75, BRD75}, {B110, BRD110}, {B134, BRD134}, {B150, BRD150}, {B200, BRD200}, {B300, BRD300}, {B600, BRD600}, {B1200, BRD1200}, {B1800, BRD1800}, {B2400, BRD2400}, {B4800, BRD4800}, {B9600, BRD9600}, {B19200, BRD19200}, {B38400, BRD38400}, {B7200, BRD7200}, {B14400, BRD14400}, {B57600, BRD57600}, {B76800, BRD76800}, {B115200, BRD115200}, {B230400, BRD230400}, {-1, -1} }; static int rpparam(tp, t) struct tty *tp; struct termios *t; { struct rp_port *rp; CHANNEL_t *cp; int unit, mynor, port, umynor; /* SG */ int oldspl, cflag, iflag, oflag, lflag; int ospeed; #ifdef RPCLOCAL int devshift; #endif umynor = (((minor(tp->t_dev) >> 16) -1) * 32); /* SG */ port = (minor(tp->t_dev) & 0x1f); /* SG */ mynor = (port + umynor); /* SG */ unit = minor_to_unit[mynor]; rp = rp_addr(unit) + port; cp = &rp->rp_channel; oldspl = spltty(); cflag = t->c_cflag; #ifdef RPCLOCAL devshift = umynor / 32; devshift = 1 << devshift; if ( devshift & RPCLOCAL ) { cflag |= CLOCAL; } #endif iflag = t->c_iflag; oflag = t->c_oflag; lflag = t->c_lflag; ospeed = ttspeedtab(t->c_ispeed, baud_table); if(ospeed < 0 || t->c_ispeed != t->c_ospeed) return(EINVAL); tp->t_ispeed = t->c_ispeed; tp->t_ospeed = t->c_ospeed; tp->t_cflag = cflag; tp->t_iflag = iflag; tp->t_oflag = oflag; tp->t_lflag = lflag; if(t->c_ospeed == 0) { sClrDTR(cp); return(0); } rp->rp_fifo_lw = ((t->c_ospeed*2) / 1000) +1; /* Set baud rate ----- we only pay attention to ispeed */ sSetDTR(cp); sSetRTS(cp); sSetBaud(cp, ospeed); if(cflag & CSTOPB) { sSetStop2(cp); } else { sSetStop1(cp); } if(cflag & PARENB) { sEnParity(cp); if(cflag & PARODD) { sSetOddParity(cp); } else { sSetEvenParity(cp); } } else { sDisParity(cp); } if((cflag & CSIZE) == CS8) { sSetData8(cp); rp->rp_imask = 0xFF; } else { sSetData7(cp); rp->rp_imask = 0x7F; } if(iflag & ISTRIP) { rp->rp_imask &= 0x7F; } if(cflag & CLOCAL) { rp->rp_intmask &= ~DELTA_CD; } else { rp->rp_intmask |= DELTA_CD; } /* Put flow control stuff here */ if(cflag & CCTS_OFLOW) { sEnCTSFlowCtl(cp); } else { sDisCTSFlowCtl(cp); } if(cflag & CRTS_IFLOW) { rp->rp_rts_iflow = 1; } else { rp->rp_rts_iflow = 0; } if(cflag & CRTS_IFLOW) { sEnRTSFlowCtl(cp); } else { sDisRTSFlowCtl(cp); } - rp_disc_optim(tp, t); + ttyldoptim(tp); if((cflag & CLOCAL) || (sGetChanStatusLo(cp) & CD_ACT)) { tp->t_state |= TS_CARR_ON; wakeup(TSA_CARR_ON(tp)); } /* tp->t_state |= TS_CAN_BYPASS_L_RINT; flags = rp->rp_channel.TxControl[3]; if(flags & SET_DTR) else if(flags & SET_RTS) else */ splx(oldspl); return(0); } -static void -rp_disc_optim(tp, t) -struct tty *tp; -struct termios *t; -{ - if(!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) - &&(!(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 void rpstart(tp) struct tty *tp; { struct rp_port *rp; CHANNEL_t *cp; struct clist *qp; int unit, mynor, port, umynor; /* SG */ char flags; int spl, xmit_fifo_room; int count, wcount; umynor = (((minor(tp->t_dev) >> 16) -1) * 32); /* SG */ port = (minor(tp->t_dev) & 0x1f); /* SG */ mynor = (port + umynor); /* SG */ unit = minor_to_unit[mynor]; rp = rp_addr(unit) + port; cp = &rp->rp_channel; flags = rp->rp_channel.TxControl[3]; spl = spltty(); if(tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(spl); return; } if(rp->rp_xmit_stopped) { sEnTransmit(cp); rp->rp_xmit_stopped = 0; } count = sGetTxCnt(cp); if(tp->t_outq.c_cc == 0) { if((tp->t_state & TS_BUSY) && (count == 0)) { tp->t_state &= ~TS_BUSY; } ttwwakeup(tp); splx(spl); return; } xmit_fifo_room = TXFIFO_SIZE - sGetTxCnt(cp); qp = &tp->t_outq; if(xmit_fifo_room > 0 && qp->c_cc > 0) { tp->t_state |= TS_BUSY; count = q_to_b( qp, (char *)rp->TxBuf, xmit_fifo_room ); wcount = count >> 1; if ( wcount ) { rp_writemultich2(cp, sGetTxRxDataIO(cp), (u_int16_t *)rp->TxBuf, wcount); } if ( count & 1 ) { rp_writech1(cp, sGetTxRxDataIO(cp), ((unsigned char *)(rp->TxBuf))[(count-1)]); } } rp->rp_restart = (qp->c_cc > 0) ? rp->rp_fifo_lw : 0; ttwwakeup(tp); splx(spl); } static void rpstop(tp, flag) register struct tty *tp; int flag; { struct rp_port *rp; CHANNEL_t *cp; int unit, mynor, port, umynor; /* SG */ int spl; umynor = (((minor(tp->t_dev) >> 16) -1) * 32); /* SG */ port = (minor(tp->t_dev) & 0x1f); /* SG */ mynor = (port + umynor); /* SG */ unit = minor_to_unit[mynor]; rp = rp_addr(unit) + port; cp = &rp->rp_channel; spl = spltty(); if(tp->t_state & TS_BUSY) { if((tp->t_state&TS_TTSTOP) == 0) { sFlushTxFIFO(cp); } else { if(rp->rp_xmit_stopped == 0) { sDisTransmit(cp); rp->rp_xmit_stopped = 1; } } } splx(spl); rpstart(tp); } diff --git a/sys/dev/si/si.c b/sys/dev/si/si.c index 4411d21db20f..e79c1caef25b 100644 --- a/sys/dev/si/si.c +++ b/sys/dev/si/si.c @@ -1,2169 +1,2145 @@ /* * Device driver for Specialix range (SI/XIO) of serial line multiplexors. * * Copyright (C) 1990, 1992, 1998 Specialix International, * Copyright (C) 1993, Andy Rutter * Copyright (C) 2000, Peter Wemm * * Originally derived from: SunOS 4.x version * Ported from BSDI version to FreeBSD by Peter Wemm. * * 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 * notices, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notices, 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 Andy Rutter of * Advanced Methods and Tools Ltd. based on original information * from Specialix International. * 4. Neither the name of Advanced Methods and Tools, nor Specialix * International may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE. * */ #include __FBSDID("$FreeBSD$"); #ifndef lint static const char si_copyright1[] = "@(#) Copyright (C) Specialix International, 1990,1992,1998", si_copyright2[] = "@(#) Copyright (C) Andy Rutter 1993", si_copyright3[] = "@(#) Copyright (C) Peter Wemm 2000"; #endif /* not lint */ #include "opt_compat.h" #include "opt_debug_si.h" #include "opt_tty.h" #include #include #if defined(COMPAT_43) || defined(COMPAT_SUNOS) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * This device driver is designed to interface the Specialix International * SI, XIO and SX range of serial multiplexor cards to FreeBSD on an ISA, * EISA or PCI bus machine. * * The controller is interfaced to the host via dual port RAM * and an interrupt. * * The code for the Host 1 (very old ISA cards) has not been tested. */ #define POLL /* turn on poller to scan for lost interrupts */ #define REALPOLL /* on each poll, scan for work regardless */ #define POLLHZ (hz/10) /* 10 times per second */ #define SI_I_HIGH_WATER (TTYHOG - 2 * SI_BUFFERSIZE) #define INT_COUNT 25000 /* max of 125 ints per second */ #define JET_INT_COUNT 100 /* max of 100 ints per second */ #define RXINT_COUNT 1 /* one rxint per 10 milliseconds */ enum si_mctl { GET, SET, BIS, BIC }; static void si_command(struct si_port *, int, int); static int si_modem(struct si_port *, enum si_mctl, int); static void si_write_enable(struct si_port *, int); static int si_Sioctl(dev_t, u_long, caddr_t, int, struct thread *); static void si_start(struct tty *); static void si_stop(struct tty *, int); static timeout_t si_lstart; -static void si_disc_optim(struct tty *tp, struct termios *t,struct si_port *pp); static void sihardclose(struct si_port *pp); static void sidtrwakeup(void *chan); #ifdef SI_DEBUG static char *si_mctl2str(enum si_mctl cmd); #endif static int siparam(struct tty *, struct termios *); static void si_modem_state(struct si_port *pp, struct tty *tp, int hi_ip); static char * si_modulename(int host_type, int uart_type); static d_open_t siopen; static d_close_t siclose; static d_write_t siwrite; static d_ioctl_t siioctl; static struct cdevsw si_cdevsw = { .d_version = D_VERSION, .d_open = siopen, .d_close = siclose, .d_write = siwrite, .d_ioctl = siioctl, .d_name = "si", .d_flags = D_TTY | D_NEEDGIANT, }; static int si_Nports; static int si_Nmodules; static int si_debug = 0; /* data, not bss, so it's patchable */ SYSCTL_INT(_machdep, OID_AUTO, si_debug, CTLFLAG_RW, &si_debug, 0, ""); TUNABLE_INT("machdep.si_debug", &si_debug); static int si_numunits; devclass_t si_devclass; #ifndef B2000 /* not standard, but the hardware knows it. */ # define B2000 2000 #endif static struct speedtab bdrates[] = { { B75, CLK75, }, /* 0x0 */ { B110, CLK110, }, /* 0x1 */ { B150, CLK150, }, /* 0x3 */ { B300, CLK300, }, /* 0x4 */ { B600, CLK600, }, /* 0x5 */ { B1200, CLK1200, }, /* 0x6 */ { B2000, CLK2000, }, /* 0x7 */ { B2400, CLK2400, }, /* 0x8 */ { B4800, CLK4800, }, /* 0x9 */ { B9600, CLK9600, }, /* 0xb */ { B19200, CLK19200, }, /* 0xc */ { B38400, CLK38400, }, /* 0x2 (out of order!) */ { B57600, CLK57600, }, /* 0xd */ { B115200, CLK110, }, /* 0x1 (dupe!, 110 baud on "si") */ { -1, -1 }, }; /* populated with approx character/sec rates - translated at card * initialisation time to chars per tick of the clock */ static int done_chartimes = 0; static struct speedtab chartimes[] = { { B75, 8, }, { B110, 11, }, { B150, 15, }, { B300, 30, }, { B600, 60, }, { B1200, 120, }, { B2000, 200, }, { B2400, 240, }, { B4800, 480, }, { B9600, 960, }, { B19200, 1920, }, { B38400, 3840, }, { B57600, 5760, }, { B115200, 11520, }, { -1, -1 }, }; static volatile int in_intr = 0; /* Inside interrupt handler? */ #ifdef POLL static int si_pollrate; /* in addition to irq */ static int si_realpoll = 0; /* poll HW on timer */ SYSCTL_INT(_machdep, OID_AUTO, si_pollrate, CTLFLAG_RW, &si_pollrate, 0, ""); SYSCTL_INT(_machdep, OID_AUTO, si_realpoll, CTLFLAG_RW, &si_realpoll, 0, ""); static int init_finished = 0; static void si_poll(void *); #endif /* * Array of adapter types and the corresponding RAM size. The order of * entries here MUST match the ordinal of the adapter type. */ static char *si_type[] = { "EMPTY", "SIHOST", "SIMCA", /* FreeBSD does not support Microchannel */ "SIHOST2", "SIEISA", "SIPCI", "SXPCI", "SXISA", }; /* * We have to make an 8 bit version of bcopy, since some cards can't * deal with 32 bit I/O */ static void __inline si_bcopy(const void *src, void *dst, size_t len) { while (len--) *(((u_char *)dst)++) = *(((const u_char *)src)++); } static void __inline si_vbcopy(const volatile void *src, void *dst, size_t len) { while (len--) *(((u_char *)dst)++) = *(((const volatile u_char *)src)++); } static void __inline si_bcopyv(const void *src, volatile void *dst, size_t len) { while (len--) *(((volatile u_char *)dst)++) = *(((const u_char *)src)++); } /* * Attach the device. Initialize the card. */ int siattach(device_t dev) { int unit; struct si_softc *sc; struct si_port *pp; volatile struct si_channel *ccbp; volatile struct si_reg *regp; volatile caddr_t maddr; struct si_module *modp; struct speedtab *spt; int nmodule, nport, x, y; int uart_type; sc = device_get_softc(dev); unit = device_get_unit(dev); sc->sc_typename = si_type[sc->sc_type]; if (si_numunits < unit + 1) si_numunits = unit + 1; DPRINT((0, DBG_AUTOBOOT, "si%d: siattach\n", unit)); #ifdef POLL if (si_pollrate == 0) { si_pollrate = POLLHZ; /* in addition to irq */ #ifdef REALPOLL si_realpoll = 1; /* scan always */ #endif } #endif DPRINT((0, DBG_AUTOBOOT, "si%d: type: %s paddr: %x maddr: %x\n", unit, sc->sc_typename, sc->sc_paddr, sc->sc_maddr)); sc->sc_ports = NULL; /* mark as uninitialised */ maddr = sc->sc_maddr; /* Stop the CPU first so it won't stomp around while we load */ switch (sc->sc_type) { case SIEISA: outb(sc->sc_iobase + 2, sc->sc_irq << 4); break; case SIPCI: *(maddr+SIPCIRESET) = 0; break; case SIJETPCI: /* fall through to JET ISA */ case SIJETISA: *(maddr+SIJETCONFIG) = 0; break; case SIHOST2: *(maddr+SIPLRESET) = 0; break; case SIHOST: *(maddr+SIRESET) = 0; break; default: /* this should never happen */ printf("si%d: unsupported configuration\n", unit); return EINVAL; break; } /* OK, now lets download the download code */ if (SI_ISJET(sc->sc_type)) { DPRINT((0, DBG_DOWNLOAD, "si%d: jet_download: nbytes %d\n", unit, si3_t225_dsize)); si_bcopy(si3_t225_download, maddr + si3_t225_downloadaddr, si3_t225_dsize); DPRINT((0, DBG_DOWNLOAD, "si%d: jet_bootstrap: nbytes %d -> %x\n", unit, si3_t225_bsize, si3_t225_bootloadaddr)); si_bcopy(si3_t225_bootstrap, maddr + si3_t225_bootloadaddr, si3_t225_bsize); } else { DPRINT((0, DBG_DOWNLOAD, "si%d: si_download: nbytes %d\n", unit, si2_z280_dsize)); si_bcopy(si2_z280_download, maddr + si2_z280_downloadaddr, si2_z280_dsize); } /* Now start the CPU */ switch (sc->sc_type) { case SIEISA: /* modify the download code to tell it that it's on an EISA */ *(maddr + 0x42) = 1; outb(sc->sc_iobase + 2, (sc->sc_irq << 4) | 4); (void)inb(sc->sc_iobase + 3); /* reset interrupt */ break; case SIPCI: /* modify the download code to tell it that it's on a PCI */ *(maddr+0x42) = 1; *(maddr+SIPCIRESET) = 1; *(maddr+SIPCIINTCL) = 0; break; case SIJETPCI: *(maddr+SIJETRESET) = 0; *(maddr+SIJETCONFIG) = SIJETBUSEN|SIJETIRQEN; break; case SIJETISA: *(maddr+SIJETRESET) = 0; switch (sc->sc_irq) { case 9: *(maddr+SIJETCONFIG) = SIJETBUSEN|SIJETIRQEN|0x90; break; case 10: *(maddr+SIJETCONFIG) = SIJETBUSEN|SIJETIRQEN|0xa0; break; case 11: *(maddr+SIJETCONFIG) = SIJETBUSEN|SIJETIRQEN|0xb0; break; case 12: *(maddr+SIJETCONFIG) = SIJETBUSEN|SIJETIRQEN|0xc0; break; case 15: *(maddr+SIJETCONFIG) = SIJETBUSEN|SIJETIRQEN|0xf0; break; } break; case SIHOST: *(maddr+SIRESET_CL) = 0; *(maddr+SIINTCL_CL) = 0; break; case SIHOST2: *(maddr+SIPLRESET) = 0x10; switch (sc->sc_irq) { case 11: *(maddr+SIPLIRQ11) = 0x10; break; case 12: *(maddr+SIPLIRQ12) = 0x10; break; case 15: *(maddr+SIPLIRQ15) = 0x10; break; } *(maddr+SIPLIRQCLR) = 0x10; break; default: /* this should _REALLY_ never happen */ printf("si%d: Uh, it was supported a second ago...\n", unit); return EINVAL; } DELAY(1000000); /* wait around for a second */ regp = (struct si_reg *)maddr; y = 0; /* wait max of 5 sec for init OK */ while (regp->initstat == 0 && y++ < 10) { DELAY(500000); } switch (regp->initstat) { case 0: printf("si%d: startup timeout - aborting\n", unit); sc->sc_type = SIEMPTY; return EINVAL; case 1: if (SI_ISJET(sc->sc_type)) { /* set throttle to 100 times per second */ regp->int_count = JET_INT_COUNT; /* rx_intr_count is a NOP in Jet */ } else { /* set throttle to 125 times per second */ regp->int_count = INT_COUNT; /* rx intr max of 25 times per second */ regp->rx_int_count = RXINT_COUNT; } regp->int_pending = 0; /* no intr pending */ regp->int_scounter = 0; /* reset counter */ break; case 0xff: /* * No modules found, so give up on this one. */ printf("si%d: %s - no ports found\n", unit, si_type[sc->sc_type]); return 0; default: printf("si%d: download code version error - initstat %x\n", unit, regp->initstat); return EINVAL; } /* * First time around the ports just count them in order * to allocate some memory. */ nport = 0; modp = (struct si_module *)(maddr + 0x80); for (;;) { DPRINT((0, DBG_DOWNLOAD, "si%d: ccb addr 0x%x\n", unit, modp)); switch (modp->sm_type) { case TA4: DPRINT((0, DBG_DOWNLOAD, "si%d: Found old TA4 module, 4 ports\n", unit)); x = 4; break; case TA8: DPRINT((0, DBG_DOWNLOAD, "si%d: Found old TA8 module, 8 ports\n", unit)); x = 8; break; case TA4_ASIC: DPRINT((0, DBG_DOWNLOAD, "si%d: Found ASIC TA4 module, 4 ports\n", unit)); x = 4; break; case TA8_ASIC: DPRINT((0, DBG_DOWNLOAD, "si%d: Found ASIC TA8 module, 8 ports\n", unit)); x = 8; break; case MTA: DPRINT((0, DBG_DOWNLOAD, "si%d: Found CD1400 module, 8 ports\n", unit)); x = 8; break; case SXDC: DPRINT((0, DBG_DOWNLOAD, "si%d: Found SXDC module, 8 ports\n", unit)); x = 8; break; default: printf("si%d: unknown module type %d\n", unit, modp->sm_type); goto try_next; } /* this was limited in firmware and is also a driver issue */ if ((nport + x) > SI_MAXPORTPERCARD) { printf("si%d: extra ports ignored\n", unit); goto try_next; } nport += x; si_Nports += x; si_Nmodules++; try_next: if (modp->sm_next == 0) break; modp = (struct si_module *) (maddr + (unsigned)(modp->sm_next & 0x7fff)); } sc->sc_ports = (struct si_port *)malloc(sizeof(struct si_port) * nport, M_DEVBUF, M_NOWAIT | M_ZERO); if (sc->sc_ports == 0) { printf("si%d: fail to malloc memory for port structs\n", unit); return EINVAL; } sc->sc_nport = nport; /* * Scan round the ports again, this time initialising. */ pp = sc->sc_ports; nmodule = 0; modp = (struct si_module *)(maddr + 0x80); uart_type = 1000; /* arbitary, > uchar_max */ for (;;) { switch (modp->sm_type) { case TA4: nport = 4; break; case TA8: nport = 8; break; case TA4_ASIC: nport = 4; break; case TA8_ASIC: nport = 8; break; case MTA: nport = 8; break; case SXDC: nport = 8; break; default: goto try_next2; } nmodule++; ccbp = (struct si_channel *)((char *)modp + 0x100); if (uart_type == 1000) uart_type = ccbp->type; else if (uart_type != ccbp->type) printf("si%d: Warning: module %d mismatch! (%d%s != %d%s)\n", unit, nmodule, ccbp->type, si_modulename(sc->sc_type, ccbp->type), uart_type, si_modulename(sc->sc_type, uart_type)); for (x = 0; x < nport; x++, pp++, ccbp++) { pp->sp_ccb = ccbp; /* save the address */ pp->sp_tty = ttymalloc(NULL); pp->sp_pend = IDLE_CLOSE; pp->sp_state = 0; /* internal flag */ pp->sp_dtr_wait = 3 * hz; pp->sp_iin.c_iflag = TTYDEF_IFLAG; pp->sp_iin.c_oflag = TTYDEF_OFLAG; pp->sp_iin.c_cflag = TTYDEF_CFLAG; pp->sp_iin.c_lflag = TTYDEF_LFLAG; termioschars(&pp->sp_iin); pp->sp_iin.c_ispeed = pp->sp_iin.c_ospeed = TTYDEF_SPEED;; pp->sp_iout = pp->sp_iin; } try_next2: if (modp->sm_next == 0) { printf("si%d: card: %s, ports: %d, modules: %d, type: %d%s\n", unit, sc->sc_typename, sc->sc_nport, nmodule, uart_type, si_modulename(sc->sc_type, uart_type)); break; } modp = (struct si_module *) (maddr + (unsigned)(modp->sm_next & 0x7fff)); } if (done_chartimes == 0) { for (spt = chartimes ; spt->sp_speed != -1; spt++) { if ((spt->sp_code /= hz) == 0) spt->sp_code = 1; } done_chartimes = 1; } /* path name devsw minor type uid gid perm*/ for (x = 0; x < sc->sc_nport; x++) { /* sync with the manuals that start at 1 */ y = x + 1 + unit * (1 << SI_CARDSHIFT); make_dev(&si_cdevsw, x, 0, 0, 0600, "ttyA%02d", y); make_dev(&si_cdevsw, x + 0x00080, 0, 0, 0600, "cuaA%02d", y); make_dev(&si_cdevsw, x + 0x10000, 0, 0, 0600, "ttyiA%02d", y); make_dev(&si_cdevsw, x + 0x10080, 0, 0, 0600, "cuaiA%02d", y); make_dev(&si_cdevsw, x + 0x20000, 0, 0, 0600, "ttylA%02d", y); make_dev(&si_cdevsw, x + 0x20080, 0, 0, 0600, "cualA%02d", y); } make_dev(&si_cdevsw, 0x40000, 0, 0, 0600, "si_control"); return (0); } static int siopen(dev_t dev, int flag, int mode, struct thread *td) { int oldspl, error; int card, port; struct si_softc *sc; struct tty *tp; volatile struct si_channel *ccbp; struct si_port *pp; int mynor = minor(dev); /* quickly let in /dev/si_control */ if (IS_CONTROLDEV(mynor)) { if ((error = suser(td))) return(error); return(0); } card = SI_CARD(mynor); sc = devclass_get_softc(si_devclass, card); if (sc == NULL) return (ENXIO); if (sc->sc_type == SIEMPTY) { DPRINT((0, DBG_OPEN|DBG_FAIL, "si%d: type %s??\n", card, sc->sc_typename)); return(ENXIO); } port = SI_PORT(mynor); if (port >= sc->sc_nport) { DPRINT((0, DBG_OPEN|DBG_FAIL, "si%d: nports %d\n", card, sc->sc_nport)); return(ENXIO); } #ifdef POLL /* * We've now got a device, so start the poller. */ if (init_finished == 0) { timeout(si_poll, (caddr_t)0L, si_pollrate); init_finished = 1; } #endif /* initial/lock device */ if (IS_STATE(mynor)) { return(0); } pp = sc->sc_ports + port; tp = pp->sp_tty; /* the "real" tty */ dev->si_tty = tp; ccbp = pp->sp_ccb; /* Find control block */ DPRINT((pp, DBG_ENTRY|DBG_OPEN, "siopen(%s,%x,%x,%x)\n", devtoname(dev), flag, mode, td)); oldspl = spltty(); /* Keep others out */ error = 0; open_top: while (pp->sp_state & SS_DTR_OFF) { error = tsleep(&pp->sp_dtr_wait, TTIPRI|PCATCH, "sidtr", 0); if (error != 0) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialised. * handle conflicts. */ if (IS_CALLOUT(mynor)) { if (!pp->sp_active_out) { error = EBUSY; goto out; } } else { if (pp->sp_active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&pp->sp_active_out, TTIPRI|PCATCH, "sibi", 0); if (error != 0) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && suser(td)) { DPRINT((pp, DBG_OPEN|DBG_FAIL, "already open and EXCLUSIVE set\n")); error = EBUSY; goto out; } } else { /* * The device isn't open, so there are no conflicts. * Initialize it. Avoid sleep... :-) */ DPRINT((pp, DBG_OPEN, "first open\n")); tp->t_oproc = si_start; tp->t_stop = si_stop; tp->t_param = siparam; tp->t_dev = dev; tp->t_termios = mynor & SI_CALLOUT_MASK ? pp->sp_iout : pp->sp_iin; (void) si_modem(pp, SET, TIOCM_DTR|TIOCM_RTS); ++pp->sp_wopeners; /* in case of sleep in siparam */ error = siparam(tp, &tp->t_termios); --pp->sp_wopeners; if (error != 0) goto out; /* XXX: we should goto_top if siparam slept */ /* set initial DCD state */ pp->sp_last_hi_ip = ccbp->hi_ip; if ((pp->sp_last_hi_ip & IP_DCD) || IS_CALLOUT(mynor)) { ttyld_modem(tp, 1); } } /* whoops! we beat the close! */ if (pp->sp_state & SS_CLOSING) { /* try and stop it from proceeding to bash the hardware */ pp->sp_state &= ~SS_CLOSING; } /* * Wait for DCD if necessary */ if (!(tp->t_state & TS_CARR_ON) && !IS_CALLOUT(mynor) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++pp->sp_wopeners; DPRINT((pp, DBG_OPEN, "sleeping for carrier\n")); error = tsleep(TSA_CARR_ON(tp), TTIPRI|PCATCH, "sidcd", 0); --pp->sp_wopeners; if (error != 0) goto out; goto open_top; } error = ttyld_open(tp, dev); - si_disc_optim(tp, &tp->t_termios, pp); + pp->sp_hotchar = ttyldoptim(tp); if (tp->t_state & TS_ISOPEN && IS_CALLOUT(mynor)) pp->sp_active_out = TRUE; pp->sp_state |= SS_OPEN; /* made it! */ out: splx(oldspl); DPRINT((pp, DBG_OPEN, "leaving siopen\n")); if (!(tp->t_state & TS_ISOPEN) && pp->sp_wopeners == 0) sihardclose(pp); return(error); } static int siclose(dev_t dev, int flag, int mode, struct thread *td) { struct si_port *pp; struct tty *tp; int oldspl; int error = 0; int mynor = minor(dev); if (IS_SPECIAL(mynor)) return(0); oldspl = spltty(); pp = MINOR2PP(mynor); tp = pp->sp_tty; DPRINT((pp, DBG_ENTRY|DBG_CLOSE, "siclose(%s,%x,%x,%x) sp_state:%x\n", devtoname(dev), flag, mode, td, pp->sp_state)); /* did we sleep and loose a race? */ if (pp->sp_state & SS_CLOSING) { /* error = ESOMETING? */ goto out; } /* begin race detection.. */ pp->sp_state |= SS_CLOSING; si_write_enable(pp, 0); /* block writes for ttywait() */ /* THIS MAY SLEEP IN TTYWAIT!!! */ ttyld_close(tp, flag); si_write_enable(pp, 1); /* did we sleep and somebody started another open? */ if (!(pp->sp_state & SS_CLOSING)) { /* error = ESOMETING? */ goto out; } /* ok. we are now still on the right track.. nuke the hardware */ if (pp->sp_state & SS_LSTART) { untimeout(si_lstart, (caddr_t)pp, pp->lstart_ch); pp->sp_state &= ~SS_LSTART; } sihardclose(pp); ttyclose(tp); pp->sp_state &= ~SS_OPEN; out: DPRINT((pp, DBG_CLOSE|DBG_EXIT, "close done, returning\n")); splx(oldspl); return(error); } static void sihardclose(struct si_port *pp) { int oldspl; struct tty *tp; volatile struct si_channel *ccbp; oldspl = spltty(); tp = pp->sp_tty; ccbp = pp->sp_ccb; /* Find control block */ if (tp->t_cflag & HUPCL || (!pp->sp_active_out && !(ccbp->hi_ip & IP_DCD) && !(pp->sp_iin.c_cflag && CLOCAL)) || !(tp->t_state & TS_ISOPEN)) { (void) si_modem(pp, BIC, TIOCM_DTR|TIOCM_RTS); (void) si_command(pp, FCLOSE, SI_NOWAIT); if (pp->sp_dtr_wait != 0) { timeout(sidtrwakeup, pp, pp->sp_dtr_wait); pp->sp_state |= SS_DTR_OFF; } } pp->sp_active_out = FALSE; wakeup(&pp->sp_active_out); wakeup(TSA_CARR_ON(tp)); splx(oldspl); } /* * called at splsoftclock()... */ static void sidtrwakeup(void *chan) { struct si_port *pp; int oldspl; oldspl = spltty(); pp = (struct si_port *)chan; pp->sp_state &= ~SS_DTR_OFF; wakeup(&pp->sp_dtr_wait); splx(oldspl); } static int siwrite(dev_t dev, struct uio *uio, int flag) { struct si_port *pp; struct tty *tp; int error = 0; int mynor = minor(dev); int oldspl; if (IS_SPECIAL(mynor)) { DPRINT((0, DBG_ENTRY|DBG_FAIL|DBG_WRITE, "siwrite(CONTROLDEV!!)\n")); return(ENODEV); } pp = MINOR2PP(mynor); tp = pp->sp_tty; DPRINT((pp, DBG_WRITE, "siwrite(%s,%x,%x)\n", devtoname(dev), uio, flag)); oldspl = spltty(); /* * If writes are currently blocked, wait on the "real" tty */ while (pp->sp_state & SS_BLOCKWRITE) { pp->sp_state |= SS_WAITWRITE; DPRINT((pp, DBG_WRITE, "in siwrite, wait for SS_BLOCKWRITE to clear\n")); if ((error = ttysleep(tp, (caddr_t)pp, TTOPRI|PCATCH, "siwrite", tp->t_timeout))) { if (error == EWOULDBLOCK) error = EIO; goto out; } } error = ttyld_write(tp, uio, flag); out: splx(oldspl); return (error); } static int siioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td) { struct si_port *pp; struct tty *tp; int error; int mynor = minor(dev); int oldspl; int blocked = 0; #if defined(COMPAT_43) u_long oldcmd; struct termios term; #endif if (IS_SI_IOCTL(cmd)) return(si_Sioctl(dev, cmd, data, flag, td)); pp = MINOR2PP(mynor); tp = pp->sp_tty; DPRINT((pp, DBG_ENTRY|DBG_IOCTL, "siioctl(%s,%lx,%x,%x)\n", devtoname(dev), cmd, data, flag)); if (IS_STATE(mynor)) { struct termios *ct; switch (mynor & SI_STATE_MASK) { case SI_INIT_STATE_MASK: ct = IS_CALLOUT(mynor) ? &pp->sp_iout : &pp->sp_iin; break; case SI_LOCK_STATE_MASK: ct = IS_CALLOUT(mynor) ? &pp->sp_lout : &pp->sp_lin; break; default: return (ENODEV); } 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); default: return (ENOTTY); } } /* * Do the old-style ioctl compat routines... */ #if defined(COMPAT_43) 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 /* * Do the initial / lock state business */ if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { int cc; struct termios *dt = (struct termios *)data; struct termios *lt = mynor & SI_CALLOUT_MASK ? &pp->sp_lout : &pp->sp_lin; 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); 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; } /* * Block user-level writes to give the ttywait() * a chance to completely drain for commands * that require the port to be in a quiescent state. */ switch (cmd) { case TIOCSETAW: case TIOCSETAF: case TIOCDRAIN: #ifdef COMPAT_43 case TIOCSETP: #endif blocked++; /* block writes for ttywait() and siparam() */ si_write_enable(pp, 0); } error = ttyioctl(dev, cmd, data, flag, td); - si_disc_optim(tp, &tp->t_termios, pp); + pp->sp_hotchar = ttyldoptim(tp); if (error != ENOTTY) goto out; oldspl = spltty(); error = 0; switch (cmd) { case TIOCSBRK: si_command(pp, SBREAK, SI_WAIT); break; case TIOCCBRK: si_command(pp, EBREAK, SI_WAIT); break; case TIOCSDTR: (void) si_modem(pp, SET, TIOCM_DTR|TIOCM_RTS); break; case TIOCCDTR: (void) si_modem(pp, SET, 0); break; case TIOCMSET: (void) si_modem(pp, SET, *(int *)data); break; case TIOCMBIS: (void) si_modem(pp, BIS, *(int *)data); break; case TIOCMBIC: (void) si_modem(pp, BIC, *(int *)data); break; case TIOCMGET: *(int *)data = si_modem(pp, GET, 0); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(td); if (error == 0) pp->sp_dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = pp->sp_dtr_wait * 100 / hz; break; default: error = ENOTTY; } splx(oldspl); out: DPRINT((pp, DBG_IOCTL|DBG_EXIT, "siioctl ret %d\n", error)); if (blocked) si_write_enable(pp, 1); return(error); } /* * Handle the Specialix ioctls. All MUST be called via the CONTROL device */ static int si_Sioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td) { struct si_softc *xsc; struct si_port *xpp; volatile struct si_reg *regp; struct si_tcsi *dp; struct si_pstat *sps; int *ip, error = 0; int oldspl; int card, port; int mynor = minor(dev); DPRINT((0, DBG_ENTRY|DBG_IOCTL, "si_Sioctl(%s,%lx,%x,%x)\n", devtoname(dev), cmd, data, flag)); #if 1 DPRINT((0, DBG_IOCTL, "TCSI_PORT=%x\n", TCSI_PORT)); DPRINT((0, DBG_IOCTL, "TCSI_CCB=%x\n", TCSI_CCB)); DPRINT((0, DBG_IOCTL, "TCSI_TTY=%x\n", TCSI_TTY)); #endif if (!IS_CONTROLDEV(mynor)) { DPRINT((0, DBG_IOCTL|DBG_FAIL, "not called from control device!\n")); return(ENODEV); } oldspl = spltty(); /* better safe than sorry */ ip = (int *)data; #define SUCHECK if ((error = suser(td))) goto out switch (cmd) { case TCSIPORTS: *ip = si_Nports; goto out; case TCSIMODULES: *ip = si_Nmodules; goto out; case TCSISDBG_ALL: SUCHECK; si_debug = *ip; goto out; case TCSIGDBG_ALL: *ip = si_debug; goto out; default: /* * Check that a controller for this port exists */ /* may also be a struct si_pstat, a superset of si_tcsi */ dp = (struct si_tcsi *)data; sps = (struct si_pstat *)data; card = dp->tc_card; xsc = devclass_get_softc(si_devclass, card); /* check.. */ if (xsc == NULL || xsc->sc_type == SIEMPTY) { error = ENOENT; goto out; } /* * And check that a port exists */ port = dp->tc_port; if (port < 0 || port >= xsc->sc_nport) { error = ENOENT; goto out; } xpp = xsc->sc_ports + port; regp = (struct si_reg *)xsc->sc_maddr; } switch (cmd) { case TCSIDEBUG: #ifdef SI_DEBUG SUCHECK; if (xpp->sp_debug) xpp->sp_debug = 0; else { xpp->sp_debug = DBG_ALL; DPRINT((xpp, DBG_IOCTL, "debug toggled %s\n", (xpp->sp_debug&DBG_ALL)?"ON":"OFF")); } break; #else error = ENODEV; goto out; #endif case TCSISDBG_LEVEL: case TCSIGDBG_LEVEL: #ifdef SI_DEBUG if (cmd == TCSIGDBG_LEVEL) { dp->tc_dbglvl = xpp->sp_debug; } else { SUCHECK; xpp->sp_debug = dp->tc_dbglvl; } break; #else error = ENODEV; goto out; #endif case TCSIGRXIT: dp->tc_int = regp->rx_int_count; break; case TCSIRXIT: SUCHECK; regp->rx_int_count = dp->tc_int; break; case TCSIGIT: dp->tc_int = regp->int_count; break; case TCSIIT: SUCHECK; regp->int_count = dp->tc_int; break; case TCSISTATE: dp->tc_int = xpp->sp_ccb->hi_ip; break; /* these next three use a different structure */ case TCSI_PORT: SUCHECK; si_bcopy(xpp, &sps->tc_siport, sizeof(sps->tc_siport)); break; case TCSI_CCB: SUCHECK; si_vbcopy(xpp->sp_ccb, &sps->tc_ccb, sizeof(sps->tc_ccb)); break; case TCSI_TTY: SUCHECK; si_bcopy(xpp->sp_tty, &sps->tc_tty, sizeof(sps->tc_tty)); break; default: error = EINVAL; goto out; } out: splx(oldspl); return(error); /* success */ } /* * siparam() : Configure line params * called at spltty(); * this may sleep, does not flush, nor wait for drain, nor block writes * caller must arrange this if it's important.. */ static int siparam(struct tty *tp, struct termios *t) { struct si_port *pp = TP2PP(tp); volatile struct si_channel *ccbp; int oldspl, cflag, iflag, oflag, lflag; int error = 0; /* shutup gcc */ int ispeed = 0; /* shutup gcc */ int ospeed = 0; /* shutup gcc */ BYTE val; DPRINT((pp, DBG_ENTRY|DBG_PARAM, "siparam(%x,%x)\n", tp, t)); cflag = t->c_cflag; iflag = t->c_iflag; oflag = t->c_oflag; lflag = t->c_lflag; DPRINT((pp, DBG_PARAM, "OFLAG 0x%x CFLAG 0x%x IFLAG 0x%x LFLAG 0x%x\n", oflag, cflag, iflag, lflag)); /* XXX - if Jet host and SXDC module, use extended baud rates */ /* if not hung up.. */ if (t->c_ospeed != 0) { /* translate baud rate to firmware values */ ospeed = ttspeedtab(t->c_ospeed, bdrates); ispeed = t->c_ispeed ? ttspeedtab(t->c_ispeed, bdrates) : ospeed; /* enforce legit baud rate */ if (ospeed < 0 || ispeed < 0) return (EINVAL); } oldspl = spltty(); ccbp = pp->sp_ccb; /* ========== set hi_break ========== */ val = 0; if (iflag & IGNBRK) /* Breaks */ val |= BR_IGN; if (iflag & BRKINT) /* Interrupt on break? */ val |= BR_INT; if (iflag & PARMRK) /* Parity mark? */ val |= BR_PARMRK; if (iflag & IGNPAR) /* Ignore chars with parity errors? */ val |= BR_PARIGN; ccbp->hi_break = val; /* ========== set hi_csr ========== */ /* if not hung up.. */ if (t->c_ospeed != 0) { /* Set I/O speeds */ val = (ispeed << 4) | ospeed; } ccbp->hi_csr = val; /* ========== set hi_mr2 ========== */ val = 0; if (cflag & CSTOPB) /* Stop bits */ val |= MR2_2_STOP; else val |= MR2_1_STOP; /* * Enable H/W RTS/CTS handshaking. The default TA/MTA is * a DCE, hence the reverse sense of RTS and CTS */ /* Output Flow - RTS must be raised before data can be sent */ if (cflag & CCTS_OFLOW) val |= MR2_RTSCONT; ccbp->hi_mr2 = val; /* ========== set hi_mr1 ========== */ val = 0; if (!(cflag & PARENB)) /* Parity */ val |= MR1_NONE; else val |= MR1_WITH; if (cflag & PARODD) val |= MR1_ODD; if ((cflag & CS8) == CS8) { /* 8 data bits? */ val |= MR1_8_BITS; } else if ((cflag & CS7) == CS7) { /* 7 data bits? */ val |= MR1_7_BITS; } else if ((cflag & CS6) == CS6) { /* 6 data bits? */ val |= MR1_6_BITS; } else { /* Must be 5 */ val |= MR1_5_BITS; } /* * Enable H/W RTS/CTS handshaking. The default TA/MTA is * a DCE, hence the reverse sense of RTS and CTS */ /* Input Flow - CTS is raised when port is ready to receive data */ if (cflag & CRTS_IFLOW) val |= MR1_CTSCONT; ccbp->hi_mr1 = val; /* ========== set hi_mask ========== */ val = 0xff; if ((cflag & CS8) == CS8) { /* 8 data bits? */ val &= 0xFF; } else if ((cflag & CS7) == CS7) { /* 7 data bits? */ val &= 0x7F; } else if ((cflag & CS6) == CS6) { /* 6 data bits? */ val &= 0x3F; } else { /* Must be 5 */ val &= 0x1F; } if (iflag & ISTRIP) val &= 0x7F; ccbp->hi_mask = val; /* ========== set hi_prtcl ========== */ val = SP_DCEN; /* Monitor DCD always, or TIOCMGET misses it */ if (iflag & IXANY) val |= SP_TANY; if (iflag & IXON) val |= SP_TXEN; if (iflag & IXOFF) val |= SP_RXEN; if (iflag & INPCK) val |= SP_PAEN; ccbp->hi_prtcl = val; /* ========== set hi_{rx|tx}{on|off} ========== */ /* XXX: the card TOTALLY shields us from the flow control... */ ccbp->hi_txon = t->c_cc[VSTART]; ccbp->hi_txoff = t->c_cc[VSTOP]; ccbp->hi_rxon = t->c_cc[VSTART]; ccbp->hi_rxoff = t->c_cc[VSTOP]; /* ========== send settings to the card ========== */ /* potential sleep here */ if (ccbp->hi_stat == IDLE_CLOSE) /* Not yet open */ si_command(pp, LOPEN, SI_WAIT); /* open it */ else si_command(pp, CONFIG, SI_WAIT); /* change params */ /* ========== set DTR etc ========== */ /* Hangup if ospeed == 0 */ if (t->c_ospeed == 0) { (void) si_modem(pp, BIC, TIOCM_DTR|TIOCM_RTS); } else { /* * If the previous speed was 0, may need to re-enable * the modem signals */ (void) si_modem(pp, SET, TIOCM_DTR|TIOCM_RTS); } DPRINT((pp, DBG_PARAM, "siparam, complete: MR1 %x MR2 %x HI_MASK %x PRTCL %x HI_BREAK %x\n", ccbp->hi_mr1, ccbp->hi_mr2, ccbp->hi_mask, ccbp->hi_prtcl, ccbp->hi_break)); splx(oldspl); return(error); } /* * Enable or Disable the writes to this channel... * "state" -> enabled = 1; disabled = 0; */ static void si_write_enable(struct si_port *pp, int state) { int oldspl; oldspl = spltty(); if (state) { pp->sp_state &= ~SS_BLOCKWRITE; if (pp->sp_state & SS_WAITWRITE) { pp->sp_state &= ~SS_WAITWRITE; /* thunder away! */ wakeup(pp); } } else { pp->sp_state |= SS_BLOCKWRITE; } splx(oldspl); } /* * Set/Get state of modem control lines. * Due to DCE-like behaviour of the adapter, some signals need translation: * TIOCM_DTR DSR * TIOCM_RTS CTS */ static int si_modem(struct si_port *pp, enum si_mctl cmd, int bits) { volatile struct si_channel *ccbp; int x; DPRINT((pp, DBG_ENTRY|DBG_MODEM, "si_modem(%x,%s,%x)\n", pp, si_mctl2str(cmd), bits)); ccbp = pp->sp_ccb; /* Find channel address */ switch (cmd) { case GET: x = ccbp->hi_ip; bits = TIOCM_LE; if (x & IP_DCD) bits |= TIOCM_CAR; if (x & IP_DTR) bits |= TIOCM_DTR; if (x & IP_RTS) bits |= TIOCM_RTS; if (x & IP_RI) bits |= TIOCM_RI; return(bits); case SET: ccbp->hi_op &= ~(OP_DSR|OP_CTS); /* fall through */ case BIS: x = 0; if (bits & TIOCM_DTR) x |= OP_DSR; if (bits & TIOCM_RTS) x |= OP_CTS; ccbp->hi_op |= x; break; case BIC: if (bits & TIOCM_DTR) ccbp->hi_op &= ~OP_DSR; if (bits & TIOCM_RTS) ccbp->hi_op &= ~OP_CTS; } return 0; } /* * Handle change of modem state */ static void si_modem_state(struct si_port *pp, struct tty *tp, int hi_ip) { /* if a modem dev */ if (hi_ip & IP_DCD) { if (!(pp->sp_last_hi_ip & IP_DCD)) { DPRINT((pp, DBG_INTR, "modem carr on t_line %d\n", tp->t_line)); (void)ttyld_modem(tp, 1); } } else { if (pp->sp_last_hi_ip & IP_DCD) { DPRINT((pp, DBG_INTR, "modem carr off\n")); if (ttyld_modem(tp, 0)) (void) si_modem(pp, SET, 0); } } pp->sp_last_hi_ip = hi_ip; } /* * Poller to catch missed interrupts. * * Note that the SYSV Specialix drivers poll at 100 times per second to get * better response. We could really use a "periodic" version timeout(). :-) */ #ifdef POLL static void si_poll(void *nothing) { struct si_softc *sc; int i; volatile struct si_reg *regp; struct si_port *pp; int lost, oldspl, port; DPRINT((0, DBG_POLL, "si_poll()\n")); oldspl = spltty(); if (in_intr) goto out; lost = 0; for (i = 0; i < si_numunits; i++) { sc = devclass_get_softc(si_devclass, i); if (sc == NULL || sc->sc_type == SIEMPTY) continue; regp = (struct si_reg *)sc->sc_maddr; /* * See if there has been a pending interrupt for 2 seconds * or so. The test (int_scounter >= 200) won't correspond * to 2 seconds if int_count gets changed. */ if (regp->int_pending != 0) { if (regp->int_scounter >= 200 && regp->initstat == 1) { printf("si%d: lost intr\n", i); lost++; } } else { regp->int_scounter = 0; } /* * gripe about no input flow control.. */ pp = sc->sc_ports; for (port = 0; port < sc->sc_nport; pp++, port++) { if (pp->sp_delta_overflows > 0) { printf("si%d: %d tty level buffer overflows\n", i, pp->sp_delta_overflows); pp->sp_delta_overflows = 0; } } } if (lost || si_realpoll) si_intr(NULL); /* call intr with fake vector */ out: splx(oldspl); timeout(si_poll, (caddr_t)0L, si_pollrate); } #endif /* ifdef POLL */ /* * The interrupt handler polls ALL ports on ALL adapters each time * it is called. */ static BYTE si_rxbuf[SI_BUFFERSIZE]; /* input staging area */ static BYTE si_txbuf[SI_BUFFERSIZE]; /* output staging area */ void si_intr(void *arg) { struct si_softc *sc; struct si_port *pp; volatile struct si_channel *ccbp; struct tty *tp; volatile caddr_t maddr; BYTE op, ip; int x, card, port, n, i, isopen; volatile BYTE *z; BYTE c; sc = arg; DPRINT((0, arg == NULL ? DBG_POLL:DBG_INTR, "si_intr\n")); if (in_intr) return; in_intr = 1; /* * When we get an int we poll all the channels and do ALL pending * work, not just the first one we find. This allows all cards to * share the same vector. * * XXX - But if we're sharing the vector with something that's NOT * a SI/XIO/SX card, we may be making more work for ourselves. */ for (card = 0; card < si_numunits; card++) { sc = devclass_get_softc(si_devclass, card); if (sc == NULL || sc->sc_type == SIEMPTY) continue; /* * First, clear the interrupt */ switch(sc->sc_type) { case SIHOST: maddr = sc->sc_maddr; ((volatile struct si_reg *)maddr)->int_pending = 0; /* flag nothing pending */ *(maddr+SIINTCL) = 0x00; /* Set IRQ clear */ *(maddr+SIINTCL_CL) = 0x00; /* Clear IRQ clear */ break; case SIHOST2: maddr = sc->sc_maddr; ((volatile struct si_reg *)maddr)->int_pending = 0; *(maddr+SIPLIRQCLR) = 0x00; *(maddr+SIPLIRQCLR) = 0x10; break; case SIPCI: maddr = sc->sc_maddr; ((volatile struct si_reg *)maddr)->int_pending = 0; *(maddr+SIPCIINTCL) = 0x0; break; case SIJETPCI: /* fall through to JETISA case */ case SIJETISA: maddr = sc->sc_maddr; ((volatile struct si_reg *)maddr)->int_pending = 0; *(maddr+SIJETINTCL) = 0x0; break; case SIEISA: maddr = sc->sc_maddr; ((volatile struct si_reg *)maddr)->int_pending = 0; (void)inb(sc->sc_iobase + 3); break; case SIEMPTY: default: continue; } ((volatile struct si_reg *)maddr)->int_scounter = 0; /* * check each port */ for (pp = sc->sc_ports, port = 0; port < sc->sc_nport; pp++, port++) { ccbp = pp->sp_ccb; tp = pp->sp_tty; /* * See if a command has completed ? */ if (ccbp->hi_stat != pp->sp_pend) { DPRINT((pp, DBG_INTR, "si_intr hi_stat = 0x%x, pend = %d\n", ccbp->hi_stat, pp->sp_pend)); switch(pp->sp_pend) { case LOPEN: case MPEND: case MOPEN: case CONFIG: case SBREAK: case EBREAK: pp->sp_pend = ccbp->hi_stat; /* sleeping in si_command */ wakeup(&pp->sp_state); break; default: pp->sp_pend = ccbp->hi_stat; } } /* * Continue on if it's closed */ if (ccbp->hi_stat == IDLE_CLOSE) { continue; } /* * Do modem state change if not a local device */ si_modem_state(pp, tp, ccbp->hi_ip); /* * Check to see if we should 'receive' characters. */ if (tp->t_state & TS_CONNECTED && tp->t_state & TS_ISOPEN) isopen = 1; else isopen = 0; /* * Do input break processing */ if (ccbp->hi_state & ST_BREAK) { if (isopen) { ttyld_rint(tp, TTY_BI); } ccbp->hi_state &= ~ST_BREAK; /* A Bit iffy this */ DPRINT((pp, DBG_INTR, "si_intr break\n")); } /* * Do RX stuff - if not open then dump any characters. * XXX: This is VERY messy and needs to be cleaned up. * * XXX: can we leave data in the host adapter buffer * when the clists are full? That may be dangerous * if the user cannot get an interrupt signal through. */ more_rx: /* XXX Sorry. the nesting was driving me bats! :-( */ if (!isopen) { ccbp->hi_rxopos = ccbp->hi_rxipos; goto end_rx; } /* * If the tty input buffers are blocked, stop emptying * the incoming buffers and let the auto flow control * assert.. */ if (tp->t_state & TS_TBLOCK) { goto end_rx; } /* * Process read characters if not skipped above */ op = ccbp->hi_rxopos; ip = ccbp->hi_rxipos; c = ip - op; if (c == 0) { goto end_rx; } n = c & 0xff; if (n > 250) n = 250; DPRINT((pp, DBG_INTR, "n = %d, op = %d, ip = %d\n", n, op, ip)); /* * Suck characters out of host card buffer into the * "input staging buffer" - so that we dont leave the * host card in limbo while we're possibly echoing * characters and possibly flushing input inside the * ldisc l_rint() routine. */ if (n <= SI_BUFFERSIZE - op) { DPRINT((pp, DBG_INTR, "\tsingle copy\n")); z = ccbp->hi_rxbuf + op; si_vbcopy(z, si_rxbuf, n); op += n; } else { x = SI_BUFFERSIZE - op; DPRINT((pp, DBG_INTR, "\tdouble part 1 %d\n", x)); z = ccbp->hi_rxbuf + op; si_vbcopy(z, si_rxbuf, x); DPRINT((pp, DBG_INTR, "\tdouble part 2 %d\n", n - x)); z = ccbp->hi_rxbuf; si_vbcopy(z, si_rxbuf + x, n - x); op += n; } /* clear collected characters from buffer */ ccbp->hi_rxopos = op; DPRINT((pp, DBG_INTR, "n = %d, op = %d, ip = %d\n", n, op, ip)); /* * at this point... * n = number of chars placed in si_rxbuf */ /* * Avoid the grotesquely inefficient lineswitch * routine (ttyinput) in "raw" mode. It usually * takes about 450 instructions (that's without * canonical processing or echo!). slinput is * reasonably fast (usually 40 instructions * plus call overhead). */ if (tp->t_state & TS_CAN_BYPASS_L_RINT) { /* block if the driver supports it */ if (tp->t_rawq.c_cc + n >= SI_I_HIGH_WATER && (tp->t_cflag & CRTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); tk_nin += n; tk_rawcc += n; tp->t_rawcc += n; pp->sp_delta_overflows += b_to_q((char *)si_rxbuf, n, &tp->t_rawq); ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; si_start(tp); } } else { /* * It'd be nice to not have to go through the * function call overhead for each char here. * It'd be nice to block input it, saving a * loop here and the call/return overhead. */ for(x = 0; x < n; x++) { i = si_rxbuf[x]; if (ttyld_rint(tp, i) == -1) { pp->sp_delta_overflows++; } } } goto more_rx; /* try for more until RXbuf is empty */ end_rx: /* XXX: Again, sorry about the gotos.. :-) */ /* * Do TX stuff */ ttyld_start(tp); } /* end of for (all ports on this controller) */ } /* end of for (all controllers) */ in_intr = 0; DPRINT((0, arg == NULL ? DBG_POLL:DBG_INTR, "end si_intr\n")); } /* * Nudge the transmitter... * * XXX: I inherited some funny code here. It implies the host card only * interrupts when the transmit buffer reaches the low-water-mark, and does * not interrupt when it's actually hits empty. In some cases, we have * processes waiting for complete drain, and we need to simulate an interrupt * about when we think the buffer is going to be empty (and retry if not). * I really am not certain about this... I *need* the hardware manuals. */ static void si_start(struct tty *tp) { struct si_port *pp; volatile struct si_channel *ccbp; struct clist *qp; BYTE ipos; int nchar; int oldspl, count, n, amount, buffer_full; oldspl = spltty(); qp = &tp->t_outq; pp = TP2PP(tp); DPRINT((pp, DBG_ENTRY|DBG_START, "si_start(%x) t_state %x sp_state %x t_outq.c_cc %d\n", tp, tp->t_state, pp->sp_state, qp->c_cc)); if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP)) goto out; buffer_full = 0; ccbp = pp->sp_ccb; count = (int)ccbp->hi_txipos - (int)ccbp->hi_txopos; DPRINT((pp, DBG_START, "count %d\n", (BYTE)count)); while ((nchar = qp->c_cc) > 0) { if ((BYTE)count >= 255) { buffer_full++; break; } amount = min(nchar, (255 - (BYTE)count)); ipos = (unsigned int)ccbp->hi_txipos; n = q_to_b(&tp->t_outq, si_txbuf, amount); /* will it fit in one lump? */ if ((SI_BUFFERSIZE - ipos) >= n) { si_bcopyv(si_txbuf, &ccbp->hi_txbuf[ipos], n); } else { si_bcopyv(si_txbuf, &ccbp->hi_txbuf[ipos], SI_BUFFERSIZE - ipos); si_bcopyv(si_txbuf + (SI_BUFFERSIZE - ipos), &ccbp->hi_txbuf[0], n - (SI_BUFFERSIZE - ipos)); } ccbp->hi_txipos += n; count = (int)ccbp->hi_txipos - (int)ccbp->hi_txopos; } if (count != 0 && nchar == 0) { tp->t_state |= TS_BUSY; } else { tp->t_state &= ~TS_BUSY; } /* wakeup time? */ ttwwakeup(tp); DPRINT((pp, DBG_START, "count %d, nchar %d, tp->t_state 0x%x\n", (BYTE)count, nchar, tp->t_state)); if (tp->t_state & TS_BUSY) { int time; time = ttspeedtab(tp->t_ospeed, chartimes); if (time > 0) { if (time < nchar) time = nchar / time; else time = 2; } else { DPRINT((pp, DBG_START, "bad char time value! %d\n", time)); time = hz/10; } if ((pp->sp_state & (SS_LSTART|SS_INLSTART)) == SS_LSTART) { untimeout(si_lstart, (caddr_t)pp, pp->lstart_ch); } else { pp->sp_state |= SS_LSTART; } DPRINT((pp, DBG_START, "arming lstart, time=%d\n", time)); pp->lstart_ch = timeout(si_lstart, (caddr_t)pp, time); } out: splx(oldspl); DPRINT((pp, DBG_EXIT|DBG_START, "leave si_start()\n")); } /* * Note: called at splsoftclock from the timeout code * This has to deal with two things... cause wakeups while waiting for * tty drains on last process exit, and call l_start at about the right * time for protocols like ppp. */ static void si_lstart(void *arg) { struct si_port *pp = arg; struct tty *tp; int oldspl; DPRINT((pp, DBG_ENTRY|DBG_LSTART, "si_lstart(%x) sp_state %x\n", pp, pp->sp_state)); oldspl = spltty(); if ((pp->sp_state & SS_OPEN) == 0 || (pp->sp_state & SS_LSTART) == 0) { splx(oldspl); return; } pp->sp_state &= ~SS_LSTART; pp->sp_state |= SS_INLSTART; tp = pp->sp_tty; /* deal with the process exit case */ ttwwakeup(tp); /* nudge protocols - eg: ppp */ ttyld_start(tp); pp->sp_state &= ~SS_INLSTART; splx(oldspl); } /* * Stop output on a line. called at spltty(); */ static void si_stop(struct tty *tp, int rw) { volatile struct si_channel *ccbp; struct si_port *pp; pp = TP2PP(tp); ccbp = pp->sp_ccb; DPRINT((TP2PP(tp), DBG_ENTRY|DBG_STOP, "si_stop(%x,%x)\n", tp, rw)); /* XXX: must check (rw & FWRITE | FREAD) etc flushing... */ if (rw & FWRITE) { /* what level are we meant to be flushing anyway? */ if (tp->t_state & TS_BUSY) { si_command(TP2PP(tp), WFLUSH, SI_NOWAIT); tp->t_state &= ~TS_BUSY; ttwwakeup(tp); /* Bruce???? */ } } #if 1 /* XXX: this doesn't work right yet.. */ /* XXX: this may have been failing because we used to call l_rint() * while we were looping based on these two counters. Now, we collect * the data and then loop stuffing it into l_rint(), making this * useless. Should we cause this to blow away the staging buffer? */ if (rw & FREAD) { ccbp->hi_rxopos = ccbp->hi_rxipos; } #endif } /* * Issue a command to the host card CPU. */ static void si_command(struct si_port *pp, int cmd, int waitflag) { int oldspl; volatile struct si_channel *ccbp = pp->sp_ccb; int x; DPRINT((pp, DBG_ENTRY|DBG_PARAM, "si_command(%x,%x,%d): hi_stat 0x%x\n", pp, cmd, waitflag, ccbp->hi_stat)); oldspl = spltty(); /* Keep others out */ /* wait until it's finished what it was doing.. */ /* XXX: sits in IDLE_BREAK until something disturbs it or break * is turned off. */ while((x = ccbp->hi_stat) != IDLE_OPEN && x != IDLE_CLOSE && x != IDLE_BREAK && x != cmd) { if (in_intr) { /* Prevent sleep in intr */ DPRINT((pp, DBG_PARAM, "cmd intr collision - completing %d\trequested %d\n", x, cmd)); splx(oldspl); return; } else if (ttysleep(pp->sp_tty, (caddr_t)&pp->sp_state, TTIPRI|PCATCH, "sicmd1", 1)) { splx(oldspl); return; } } /* it should now be in IDLE_{OPEN|CLOSE|BREAK}, or "cmd" */ /* if there was a pending command, cause a state-change wakeup */ switch(pp->sp_pend) { case LOPEN: case MPEND: case MOPEN: case CONFIG: case SBREAK: case EBREAK: wakeup(&pp->sp_state); break; default: break; } pp->sp_pend = cmd; /* New command pending */ ccbp->hi_stat = cmd; /* Post it */ if (waitflag) { if (in_intr) { /* If in interrupt handler */ DPRINT((pp, DBG_PARAM, "attempt to sleep in si_intr - cmd req %d\n", cmd)); splx(oldspl); return; } else while(ccbp->hi_stat != IDLE_OPEN && ccbp->hi_stat != IDLE_BREAK) { if (ttysleep(pp->sp_tty, (caddr_t)&pp->sp_state, TTIPRI|PCATCH, "sicmd2", 0)) break; } } splx(oldspl); } -static void -si_disc_optim(struct tty *tp, struct termios *t, struct si_port *pp) -{ - /* - * XXX can skip a lot more cases if Smarts. Maybe - * (IGNCR | ISTRIP | IXON) in c_iflag. But perhaps we - * shouldn't skip if (TS_CNTTB | TS_LNCH) is set in t_state. - */ - if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && - (!(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; - pp->sp_hotchar = linesw[tp->t_line].l_hotchar; - DPRINT((pp, DBG_OPTIM, "bypass: %s, hotchar: %x\n", - (tp->t_state & TS_CAN_BYPASS_L_RINT) ? "on" : "off", - pp->sp_hotchar)); -} - #ifdef SI_DEBUG void si_dprintf(struct si_port *pp, int flags, const char *fmt, ...) { va_list ap; if ((pp == NULL && (si_debug&flags)) || (pp != NULL && ((pp->sp_debug&flags) || (si_debug&flags)))) { if (pp != NULL) printf("%ci%d(%d): ", 's', (int)SI_CARD(minor(pp->sp_tty->t_dev)), (int)SI_PORT(minor(pp->sp_tty->t_dev))); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); } } static char * si_mctl2str(enum si_mctl cmd) { switch (cmd) { case GET: return("GET"); case SET: return("SET"); case BIS: return("BIS"); case BIC: return("BIC"); } return("BAD"); } #endif /* DEBUG */ static char * si_modulename(int host_type, int uart_type) { switch (host_type) { /* Z280 based cards */ case SIEISA: case SIHOST2: case SIHOST: case SIPCI: switch (uart_type) { case 0: return(" (XIO)"); case 1: return(" (SI)"); } break; /* T225 based hosts */ case SIJETPCI: case SIJETISA: switch (uart_type) { case 0: return(" (SI)"); case 40: return(" (XIO)"); case 72: return(" (SXDC)"); } break; } return(""); } diff --git a/sys/dev/sio/sio.c b/sys/dev/sio/sio.c index f384e31aba18..a63625f500fd 100644 --- a/sys/dev/sio/sio.c +++ b/sys/dev/sio/sio.c @@ -1,3252 +1,3232 @@ /*- * Copyright (c) 1991 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. * 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. * * from: @(#)com.c 7.5 (Berkeley) 5/16/91 * from: i386/isa sio.c,v 1.234 */ #include __FBSDID("$FreeBSD$"); #include "opt_comconsole.h" #include "opt_compat.h" #include "opt_ddb.h" #include "opt_sio.h" /* * Serial driver, based on 386BSD-0.1 com driver. * Mostly rewritten to use pseudo-DMA. * Works for National Semiconductor NS8250-NS16550AF UARTs. * COM driver, based on HP dca driver. * * Changes for PC-Card integration: * - Added PC-Card driver table and handlers */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if DDB > 0 #include #endif #include #include #include #include #ifdef COM_ESP #include #endif #include #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) #define MINOR_TO_UNIT(mynor) ((((mynor) & ~0xffffU) >> (8 + 3)) \ | ((mynor) & 0x1f)) #define UNIT_TO_MINOR(unit) ((((unit) & ~0x1fU) << (8 + 3)) \ | ((unit) & 0x1f)) #ifdef COM_MULTIPORT /* checks in flags for multiport and which is multiport "master chip" * for a given card */ #define COM_ISMULTIPORT(flags) ((flags) & 0x01) #define COM_MPMASTER(flags) (((flags) >> 8) & 0x0ff) #define COM_NOTAST4(flags) ((flags) & 0x04) #else #define COM_ISMULTIPORT(flags) (0) #endif /* COM_MULTIPORT */ #define COM_C_IIR_TXRDYBUG 0x80000 #define COM_CONSOLE(flags) ((flags) & 0x10) #define COM_DEBUGGER(flags) ((flags) & 0x80) #define COM_FIFOSIZE(flags) (((flags) & 0xff000000) >> 24) #define COM_FORCECONSOLE(flags) ((flags) & 0x20) #define COM_IIR_TXRDYBUG(flags) ((flags) & COM_C_IIR_TXRDYBUG) #define COM_LLCONSOLE(flags) ((flags) & 0x40) #define COM_LOSESOUTINTS(flags) ((flags) & 0x08) #define COM_NOFIFO(flags) ((flags) & 0x02) #define COM_NOPROBE(flags) ((flags) & 0x40000) #define COM_NOSCR(flags) ((flags) & 0x100000) #define COM_PPSCTS(flags) ((flags) & 0x10000) #define COM_ST16650A(flags) ((flags) & 0x20000) #define COM_TI16754(flags) ((flags) & 0x200000) #define sio_getreg(com, off) \ (bus_space_read_1((com)->bst, (com)->bsh, (off))) #define sio_setreg(com, off, value) \ (bus_space_write_1((com)->bst, (com)->bsh, (off), (value))) /* * com state bits. * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher * than the other bits so that they can be tested as a group without masking * off the low bits. * * The following com and tty flags correspond closely: * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and * comstop()) * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ #define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ #define CSE_BUSYCHECK 1 /* siobusycheck() scheduled */ static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 "interrupt-level buffer overflow", #define CE_TTY_BUF_OVERFLOW 2 "tty-level buffer overflow", }; #define CE_NTYPES 3 #define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) /* types. XXX - should be elsewhere */ typedef u_int Port_t; /* hardware port */ typedef u_char bool_t; /* boolean */ /* queue of linear buffers */ struct lbq { u_char *l_head; /* next char to process */ u_char *l_tail; /* one past the last char to process */ struct lbq *l_next; /* next in queue */ bool_t l_queued; /* nonzero if queued */ }; /* com device structure */ struct com_s { u_char state; /* miscellaneous flag bits */ bool_t active_out; /* nonzero if the callout device is open */ u_char cfcr_image; /* copy of value written to CFCR */ #ifdef COM_ESP bool_t esp; /* is this unit a hayes esp board? */ #endif u_char extra_state; /* more flag bits, separate for order trick */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t loses_outints; /* nonzero if device loses output interrupts */ u_char mcr_image; /* copy of value written to MCR */ #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ bool_t no_irq; /* nonzero if irq is not attached */ bool_t gone; /* hardware disappeared */ bool_t poll; /* nonzero if polling is required */ bool_t poll_output; /* nonzero if polling for output is required */ bool_t st16650a; /* nonzero if Startech 16650A compatible */ int unit; /* unit number */ int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ u_int flags; /* copy of device flags */ u_int tx_fifo_size; u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly * because there would be too many side effects to handle conveniently. * Instead, it reads copies of the registers stored here by the * interrupt handler. */ u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ibufold; /* old input buffer, to be freed */ u_char *ihighwater; /* threshold in input buffer */ u_char *iptr; /* next free spot in input buffer */ int ibufsize; /* size of ibuf (not include error bytes) */ int ierroff; /* offset of error bytes in ibuf */ struct lbq obufq; /* head of queue of output buffers */ struct lbq obufs[2]; /* output buffers */ bus_space_tag_t bst; bus_space_handle_t bsh; Port_t data_port; /* i/o ports */ #ifdef COM_ESP Port_t esp_port; #endif Port_t int_ctl_port; Port_t int_id_port; Port_t modem_ctl_port; Port_t line_status_port; Port_t modem_status_port; struct tty *tp; /* cross reference */ /* Initial state. */ struct termios it_in; /* should be in struct tty */ struct termios it_out; /* Lock state. */ struct termios lt_in; /* should be in struct tty */ struct termios lt_out; bool_t do_timestamp; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_timestamp; struct pps_state pps; int pps_bit; #ifdef ALT_BREAK_TO_DEBUGGER int alt_brk_state; #endif u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; u_long error_counts[CE_NTYPES]; u_long rclk; struct resource *irqres; struct resource *ioportres; int ioportrid; void *cookie; dev_t devs[6]; /* * Data area for output buffers. Someday we should build the output * buffer queue without copying data. */ u_char obuf1[256]; u_char obuf2[256]; }; #ifdef COM_ESP static int espattach(struct com_s *com, Port_t esp_port); #endif static timeout_t siobusycheck; static u_int siodivisor(u_long rclk, speed_t speed); static timeout_t siodtrwakeup; static void comhardclose(struct com_s *com); static void sioinput(struct com_s *com); static void siointr1(struct com_s *com); static void siointr(void *arg); static int commctl(struct com_s *com, int bits, int how); static int comparam(struct tty *tp, struct termios *t); static void siopoll(void *); static void siosettimeout(void); static int siosetwater(struct com_s *com, speed_t speed); static void comstart(struct tty *tp); static void comstop(struct tty *tp, int rw); static timeout_t comwakeup; -static void disc_optim(struct tty *tp, struct termios *t, - struct com_s *com); char sio_driver_name[] = "sio"; static struct mtx sio_lock; static int sio_inited; /* table and macro for fast conversion from a unit number to its com struct */ devclass_t sio_devclass; #define com_addr(unit) ((struct com_s *) \ devclass_get_softc(sio_devclass, unit)) /* XXX */ static d_open_t sioopen; static d_close_t sioclose; static d_read_t sioread; static d_write_t siowrite; static d_ioctl_t sioioctl; static struct cdevsw sio_cdevsw = { .d_version = D_VERSION, .d_open = sioopen, .d_close = sioclose, .d_read = sioread, .d_write = siowrite, .d_ioctl = sioioctl, .d_name = sio_driver_name, .d_flags = D_TTY | D_NEEDGIANT, }; int comconsole = -1; static volatile speed_t comdefaultrate = CONSPEED; static u_long comdefaultrclk = DEFAULT_RCLK; SYSCTL_ULONG(_machdep, OID_AUTO, conrclk, CTLFLAG_RW, &comdefaultrclk, 0, ""); static speed_t gdbdefaultrate = GDBSPEED; SYSCTL_UINT(_machdep, OID_AUTO, gdbspeed, CTLFLAG_RW, &gdbdefaultrate, GDBSPEED, ""); static u_int com_events; /* input chars + weighted output completions */ static Port_t siocniobase; static int siocnunit = -1; static Port_t siogdbiobase; static int siogdbunit = -1; static void *sio_slow_ih; static void *sio_fast_ih; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); static int sio_numunits; #ifdef COM_ESP /* XXX configure this properly. */ /* XXX quite broken for new-bus. */ static Port_t likely_com_ports[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, }; static Port_t likely_esp_ports[] = { 0x140, 0x180, 0x280, 0 }; #endif /* * handle sysctl read/write requests for console speed * * In addition to setting comdefaultrate for I/O through /dev/console, * also set the initial and lock values for the /dev/ttyXX device * if there is one associated with the console. Finally, if the /dev/tty * device has already been open, change the speed on the open running port * itself. */ static int sysctl_machdep_comdefaultrate(SYSCTL_HANDLER_ARGS) { int error, s; speed_t newspeed; struct com_s *com; struct tty *tp; newspeed = comdefaultrate; error = sysctl_handle_opaque(oidp, &newspeed, sizeof newspeed, req); if (error || !req->newptr) return (error); comdefaultrate = newspeed; if (comconsole < 0) /* serial console not selected? */ return (0); com = com_addr(comconsole); if (com == NULL) return (ENXIO); /* * set the initial and lock rates for /dev/ttydXX and /dev/cuaXX * (note, the lock rates really are boolean -- if non-zero, disallow * speed changes) */ com->it_in.c_ispeed = com->it_in.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_out.c_ispeed = com->it_out.c_ospeed = com->lt_out.c_ispeed = com->lt_out.c_ospeed = comdefaultrate; /* * if we're open, change the running rate too */ tp = com->tp; if (tp && (tp->t_state & TS_ISOPEN)) { tp->t_termios.c_ispeed = tp->t_termios.c_ospeed = comdefaultrate; s = spltty(); error = comparam(tp, &tp->t_termios); splx(s); } return error; } SYSCTL_PROC(_machdep, OID_AUTO, conspeed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_machdep_comdefaultrate, "I", ""); /* TUNABLE_INT("machdep.conspeed", &comdefaultrate); */ #define SET_FLAG(dev, bit) device_set_flags(dev, device_get_flags(dev) | (bit)) #define CLR_FLAG(dev, bit) device_set_flags(dev, device_get_flags(dev) & ~(bit)) /* * Unload the driver and clear the table. * XXX this is mostly wrong. * XXX TODO: * This is usually called when the card is ejected, but * can be caused by a kldunload of a controller driver. * The idea is to reset the driver's view of the device * and ensure that any driver entry points such as * read and write do not hang. */ int siodetach(dev) device_t dev; { struct com_s *com; int i; com = (struct com_s *) device_get_softc(dev); if (com == NULL) { device_printf(dev, "NULL com in siounload\n"); return (0); } com->gone = TRUE; for (i = 0 ; i < 6; i++) destroy_dev(com->devs[i]); if (com->irqres) { bus_teardown_intr(dev, com->irqres, com->cookie); bus_release_resource(dev, SYS_RES_IRQ, 0, com->irqres); } if (com->ioportres) bus_release_resource(dev, SYS_RES_IOPORT, com->ioportrid, com->ioportres); if (com->tp && (com->tp->t_state & TS_ISOPEN)) { device_printf(dev, "still open, forcing close\n"); ttyld_close(com->tp, 0); ttyclose(com->tp); } else { if (com->ibuf != NULL) free(com->ibuf, M_DEVBUF); device_set_softc(dev, NULL); free(com, M_DEVBUF); } return (0); } int sioprobe(dev, xrid, rclk, noprobe) device_t dev; int xrid; u_long rclk; int noprobe; { #if 0 static bool_t already_init; device_t xdev; #endif struct com_s *com; u_int divisor; bool_t failures[10]; int fn; device_t idev; Port_t iobase; intrmask_t irqmap[4]; intrmask_t irqs; u_char mcr_image; int result; u_long xirq; u_int flags = device_get_flags(dev); int rid; struct resource *port; rid = xrid; port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, IO_COMSIZE, RF_ACTIVE); if (!port) return (ENXIO); com = malloc(sizeof(*com), M_DEVBUF, M_NOWAIT | M_ZERO); if (com == NULL) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return (ENOMEM); } device_set_softc(dev, com); com->bst = rman_get_bustag(port); com->bsh = rman_get_bushandle(port); if (rclk == 0) rclk = DEFAULT_RCLK; com->rclk = rclk; while (sio_inited != 2) if (atomic_cmpset_int(&sio_inited, 0, 1)) { mtx_init(&sio_lock, sio_driver_name, NULL, (comconsole != -1) ? MTX_SPIN | MTX_QUIET : MTX_SPIN); atomic_store_rel_int(&sio_inited, 2); } #if 0 /* * XXX this is broken - when we are first called, there are no * previously configured IO ports. We could hard code * 0x3f8, 0x2f8, 0x3e8, 0x2e8 etc but that's probably worse. * This code has been doing nothing since the conversion since * "count" is zero the first time around. */ if (!already_init) { /* * Turn off MCR_IENABLE for all likely serial ports. An unused * port with its MCR_IENABLE gate open will inhibit interrupts * from any used port that shares the interrupt vector. * XXX the gate enable is elsewhere for some multiports. */ device_t *devs; int count, i, xioport; devclass_get_devices(sio_devclass, &devs, &count); for (i = 0; i < count; i++) { xdev = devs[i]; if (device_is_enabled(xdev) && bus_get_resource(xdev, SYS_RES_IOPORT, 0, &xioport, NULL) == 0) outb(xioport + com_mcr, 0); } free(devs, M_TEMP); already_init = TRUE; } #endif if (COM_LLCONSOLE(flags)) { printf("sio%d: reserved for low-level i/o\n", device_get_unit(dev)); bus_release_resource(dev, SYS_RES_IOPORT, rid, port); device_set_softc(dev, NULL); free(com, M_DEVBUF); return (ENXIO); } /* * If the device is on a multiport card and has an AST/4 * compatible interrupt control register, initialize this * register and prepare to leave MCR_IENABLE clear in the mcr. * Otherwise, prepare to set MCR_IENABLE in the mcr. * Point idev to the device struct giving the correct id_irq. * This is the struct for the master device if there is one. */ idev = dev; mcr_image = MCR_IENABLE; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(flags)) { Port_t xiobase; u_long io; idev = devclass_get_device(sio_devclass, COM_MPMASTER(flags)); if (idev == NULL) { printf("sio%d: master device %d not configured\n", device_get_unit(dev), COM_MPMASTER(flags)); idev = dev; } if (!COM_NOTAST4(flags)) { if (bus_get_resource(idev, SYS_RES_IOPORT, 0, &io, NULL) == 0) { xiobase = io; if (bus_get_resource(idev, SYS_RES_IRQ, 0, NULL, NULL) == 0) outb(xiobase + com_scr, 0x80); else outb(xiobase + com_scr, 0); } mcr_image = 0; } } #endif /* COM_MULTIPORT */ if (bus_get_resource(idev, SYS_RES_IRQ, 0, NULL, NULL) != 0) mcr_image = 0; bzero(failures, sizeof failures); iobase = rman_get_start(port); /* * We don't want to get actual interrupts, just masked ones. * Interrupts from this line should already be masked in the ICU, * but mask them in the processor as well in case there are some * (misconfigured) shared interrupts. */ mtx_lock_spin(&sio_lock); /* EXTRA DELAY? */ /* * For the TI16754 chips, set prescaler to 1 (4 is often the * default after-reset value) as otherwise it's impossible to * get highest baudrates. */ if (COM_TI16754(flags)) { u_char cfcr, efr; cfcr = sio_getreg(com, com_cfcr); sio_setreg(com, com_cfcr, CFCR_EFR_ENABLE); efr = sio_getreg(com, com_efr); /* Unlock extended features to turn off prescaler. */ sio_setreg(com, com_efr, efr | EFR_EFE); /* Disable EFR. */ sio_setreg(com, com_cfcr, (cfcr != CFCR_EFR_ENABLE) ? cfcr : 0); /* Turn off prescaler. */ sio_setreg(com, com_mcr, sio_getreg(com, com_mcr) & ~MCR_PRESCALE); sio_setreg(com, com_cfcr, CFCR_EFR_ENABLE); sio_setreg(com, com_efr, efr); sio_setreg(com, com_cfcr, cfcr); } /* * Initialize the speed and the word size and wait long enough to * drain the maximum of 16 bytes of junk in device output queues. * The speed is undefined after a master reset and must be set * before relying on anything related to output. There may be * junk after a (very fast) soft reboot and (apparently) after * master reset. * XXX what about the UART bug avoided by waiting in comparam()? * We don't want to to wait long enough to drain at 2 bps. */ if (iobase == siocniobase) DELAY((16 + 1) * 1000000 / (comdefaultrate / 10)); else { sio_setreg(com, com_cfcr, CFCR_DLAB | CFCR_8BITS); divisor = siodivisor(rclk, SIO_TEST_SPEED); sio_setreg(com, com_dlbl, divisor & 0xff); sio_setreg(com, com_dlbh, divisor >> 8); sio_setreg(com, com_cfcr, CFCR_8BITS); DELAY((16 + 1) * 1000000 / (SIO_TEST_SPEED / 10)); } /* * Enable the interrupt gate and disable device interupts. This * should leave the device driving the interrupt line low and * guarantee an edge trigger if an interrupt can be generated. */ /* EXTRA DELAY? */ sio_setreg(com, com_mcr, mcr_image); sio_setreg(com, com_ier, 0); DELAY(1000); /* XXX */ irqmap[0] = isa_irq_pending(); /* * Attempt to set loopback mode so that we can send a null byte * without annoying any external device. */ /* EXTRA DELAY? */ sio_setreg(com, com_mcr, mcr_image | MCR_LOOPBACK); /* * Attempt to generate an output interrupt. On 8250's, setting * IER_ETXRDY generates an interrupt independent of the current * setting and independent of whether the THR is empty. On 16450's, * setting IER_ETXRDY generates an interrupt independent of the * current setting. On 16550A's, setting IER_ETXRDY only * generates an interrupt when IER_ETXRDY is not already set. */ sio_setreg(com, com_ier, IER_ETXRDY); /* * On some 16x50 incompatibles, setting IER_ETXRDY doesn't generate * an interrupt. They'd better generate one for actually doing * output. Loopback may be broken on the same incompatibles but * it's unlikely to do more than allow the null byte out. */ sio_setreg(com, com_data, 0); if (iobase == siocniobase) DELAY((1 + 2) * 1000000 / (comdefaultrate / 10)); else DELAY((1 + 2) * 1000000 / (SIO_TEST_SPEED / 10)); /* * Turn off loopback mode so that the interrupt gate works again * (MCR_IENABLE was hidden). This should leave the device driving * an interrupt line high. It doesn't matter if the interrupt * line oscillates while we are not looking at it, since interrupts * are disabled. */ /* EXTRA DELAY? */ sio_setreg(com, com_mcr, mcr_image); /* * It seems my Xircom CBEM56G Cardbus modem wants to be reset * to 8 bits *again*, or else probe test 0 will fail. * gwk@sgi.com, 4/19/2001 */ sio_setreg(com, com_cfcr, CFCR_8BITS); /* * Some PCMCIA cards (Palido 321s, DC-1S, ...) have the "TXRDY bug", * so we probe for a buggy IIR_TXRDY implementation even in the * noprobe case. We don't probe for it in the !noprobe case because * noprobe is always set for PCMCIA cards and the problem is not * known to affect any other cards. */ if (noprobe) { /* Read IIR a few times. */ for (fn = 0; fn < 2; fn ++) { DELAY(10000); failures[6] = sio_getreg(com, com_iir); } /* IIR_TXRDY should be clear. Is it? */ result = 0; if (failures[6] & IIR_TXRDY) { /* * No. We seem to have the bug. Does our fix for * it work? */ sio_setreg(com, com_ier, 0); if (sio_getreg(com, com_iir) & IIR_NOPEND) { /* Yes. We discovered the TXRDY bug! */ SET_FLAG(dev, COM_C_IIR_TXRDYBUG); } else { /* No. Just fail. XXX */ result = ENXIO; sio_setreg(com, com_mcr, 0); } } else { /* Yes. No bug. */ CLR_FLAG(dev, COM_C_IIR_TXRDYBUG); } sio_setreg(com, com_ier, 0); sio_setreg(com, com_cfcr, CFCR_8BITS); mtx_unlock_spin(&sio_lock); bus_release_resource(dev, SYS_RES_IOPORT, rid, port); if (iobase == siocniobase) result = 0; if (result != 0) { device_set_softc(dev, NULL); free(com, M_DEVBUF); } return (result); } /* * Check that * o the CFCR, IER and MCR in UART hold the values written to them * (the values happen to be all distinct - this is good for * avoiding false positive tests from bus echoes). * o an output interrupt is generated and its vector is correct. * o the interrupt goes away when the IIR in the UART is read. */ /* EXTRA DELAY? */ failures[0] = sio_getreg(com, com_cfcr) - CFCR_8BITS; failures[1] = sio_getreg(com, com_ier) - IER_ETXRDY; failures[2] = sio_getreg(com, com_mcr) - mcr_image; DELAY(10000); /* Some internal modems need this time */ irqmap[1] = isa_irq_pending(); failures[4] = (sio_getreg(com, com_iir) & IIR_IMASK) - IIR_TXRDY; DELAY(1000); /* XXX */ irqmap[2] = isa_irq_pending(); failures[6] = (sio_getreg(com, com_iir) & IIR_IMASK) - IIR_NOPEND; /* * Turn off all device interrupts and check that they go off properly. * Leave MCR_IENABLE alone. For ports without a master port, it gates * the OUT2 output of the UART to * the ICU input. Closing the gate would give a floating ICU input * (unless there is another device driving it) and spurious interrupts. * (On the system that this was first tested on, the input floats high * and gives a (masked) interrupt as soon as the gate is closed.) */ sio_setreg(com, com_ier, 0); sio_setreg(com, com_cfcr, CFCR_8BITS); /* dummy to avoid bus echo */ failures[7] = sio_getreg(com, com_ier); DELAY(1000); /* XXX */ irqmap[3] = isa_irq_pending(); failures[9] = (sio_getreg(com, com_iir) & IIR_IMASK) - IIR_NOPEND; mtx_unlock_spin(&sio_lock); irqs = irqmap[1] & ~irqmap[0]; if (bus_get_resource(idev, SYS_RES_IRQ, 0, &xirq, NULL) == 0 && ((1 << xirq) & irqs) == 0) { printf( "sio%d: configured irq %ld not in bitmap of probed irqs %#x\n", device_get_unit(dev), xirq, irqs); printf( "sio%d: port may not be enabled\n", device_get_unit(dev)); } if (bootverbose) printf("sio%d: irq maps: %#x %#x %#x %#x\n", device_get_unit(dev), irqmap[0], irqmap[1], irqmap[2], irqmap[3]); result = 0; for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) { sio_setreg(com, com_mcr, 0); result = ENXIO; if (bootverbose) { printf("sio%d: probe failed test(s):", device_get_unit(dev)); for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) printf(" %d", fn); printf("\n"); } break; } bus_release_resource(dev, SYS_RES_IOPORT, rid, port); if (iobase == siocniobase) result = 0; if (result != 0) { device_set_softc(dev, NULL); free(com, M_DEVBUF); } return (result); } #ifdef COM_ESP static int espattach(com, esp_port) struct com_s *com; Port_t esp_port; { u_char dips; u_char val; /* * Check the ESP-specific I/O port to see if we're an ESP * card. If not, return failure immediately. */ if ((inb(esp_port) & 0xf3) == 0) { printf(" port 0x%x is not an ESP board?\n", esp_port); return (0); } /* * We've got something that claims to be a Hayes ESP card. * Let's hope so. */ /* Get the dip-switch configuration */ outb(esp_port + ESP_CMD1, ESP_GETDIPS); dips = inb(esp_port + ESP_STATUS1); /* * Bits 0,1 of dips say which COM port we are. */ if (rman_get_start(com->ioportres) == likely_com_ports[dips & 0x03]) printf(" : ESP"); else { printf(" esp_port has com %d\n", dips & 0x03); return (0); } /* * Check for ESP version 2.0 or later: bits 4,5,6 = 010. */ outb(esp_port + ESP_CMD1, ESP_GETTEST); val = inb(esp_port + ESP_STATUS1); /* clear reg 1 */ val = inb(esp_port + ESP_STATUS2); if ((val & 0x70) < 0x20) { printf("-old (%o)", val & 0x70); return (0); } /* * Check for ability to emulate 16550: bit 7 == 1 */ if ((dips & 0x80) == 0) { printf(" slave"); return (0); } /* * Okay, we seem to be a Hayes ESP card. Whee. */ com->esp = TRUE; com->esp_port = esp_port; return (1); } #endif /* COM_ESP */ int sioattach(dev, xrid, rclk) device_t dev; int xrid; u_long rclk; { struct com_s *com; #ifdef COM_ESP Port_t *espp; #endif Port_t iobase; int minorbase; int unit; u_int flags; int rid; struct resource *port; int ret; rid = xrid; port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, IO_COMSIZE, RF_ACTIVE); if (!port) return (ENXIO); iobase = rman_get_start(port); unit = device_get_unit(dev); com = device_get_softc(dev); flags = device_get_flags(dev); if (unit >= sio_numunits) sio_numunits = unit + 1; /* * sioprobe() has initialized the device registers as follows: * o cfcr = CFCR_8BITS. * It is most important that CFCR_DLAB is off, so that the * data port is not hidden when we enable interrupts. * o ier = 0. * Interrupts are only enabled when the line is open. * o mcr = MCR_IENABLE, or 0 if the port has AST/4 compatible * interrupt control register or the config specifies no irq. * Keeping MCR_DTR and MCR_RTS off might stop the external * device from sending before we are ready. */ bzero(com, sizeof *com); com->unit = unit; com->ioportres = port; com->ioportrid = rid; com->bst = rman_get_bustag(port); com->bsh = rman_get_bushandle(port); com->cfcr_image = CFCR_8BITS; com->dtr_wait = 3 * hz; com->loses_outints = COM_LOSESOUTINTS(flags) != 0; com->no_irq = bus_get_resource(dev, SYS_RES_IRQ, 0, NULL, NULL) != 0; com->tx_fifo_size = 1; com->obufs[0].l_head = com->obuf1; com->obufs[1].l_head = com->obuf2; com->data_port = iobase + com_data; com->int_ctl_port = iobase + com_ier; com->int_id_port = iobase + com_iir; com->modem_ctl_port = iobase + com_mcr; com->mcr_image = inb(com->modem_ctl_port); com->line_status_port = iobase + com_lsr; com->modem_status_port = iobase + com_msr; if (rclk == 0) rclk = DEFAULT_RCLK; com->rclk = rclk; /* * We don't use all the flags from 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. */ com->it_in.c_iflag = 0; com->it_in.c_oflag = 0; com->it_in.c_cflag = TTYDEF_CFLAG; com->it_in.c_lflag = 0; if (unit == comconsole) { com->it_in.c_iflag = TTYDEF_IFLAG; com->it_in.c_oflag = TTYDEF_OFLAG; com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; com->it_in.c_lflag = TTYDEF_LFLAG; com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; com->lt_out.c_ispeed = com->lt_out.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; } else com->it_in.c_ispeed = com->it_in.c_ospeed = TTYDEF_SPEED; if (siosetwater(com, com->it_in.c_ispeed) != 0) { mtx_unlock_spin(&sio_lock); /* * Leave i/o resources allocated if this is a `cn'-level * console, so that other devices can't snarf them. */ if (iobase != siocniobase) bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return (ENOMEM); } mtx_unlock_spin(&sio_lock); termioschars(&com->it_in); com->it_out = com->it_in; /* attempt to determine UART type */ printf("sio%d: type", unit); if (!COM_ISMULTIPORT(flags) && !COM_IIR_TXRDYBUG(flags) && !COM_NOSCR(flags)) { u_char scr; u_char scr1; u_char scr2; scr = sio_getreg(com, com_scr); sio_setreg(com, com_scr, 0xa5); scr1 = sio_getreg(com, com_scr); sio_setreg(com, com_scr, 0x5a); scr2 = sio_getreg(com, com_scr); sio_setreg(com, com_scr, scr); if (scr1 != 0xa5 || scr2 != 0x5a) { printf(" 8250 or not responding"); goto determined_type; } } sio_setreg(com, com_fifo, FIFO_ENABLE | FIFO_RX_HIGH); DELAY(100); switch (inb(com->int_id_port) & IIR_FIFO_MASK) { case FIFO_RX_LOW: printf(" 16450"); break; case FIFO_RX_MEDL: printf(" 16450?"); break; case FIFO_RX_MEDH: printf(" 16550?"); break; case FIFO_RX_HIGH: if (COM_NOFIFO(flags)) { printf(" 16550A fifo disabled"); break; } com->hasfifo = TRUE; if (COM_ST16650A(flags)) { printf(" ST16650A"); com->st16650a = TRUE; com->tx_fifo_size = 32; break; } if (COM_TI16754(flags)) { printf(" TI16754"); com->tx_fifo_size = 64; break; } printf(" 16550A"); #ifdef COM_ESP for (espp = likely_esp_ports; *espp != 0; espp++) if (espattach(com, *espp)) { com->tx_fifo_size = 1024; break; } if (com->esp) break; #endif com->tx_fifo_size = COM_FIFOSIZE(flags); if (com->tx_fifo_size == 0) com->tx_fifo_size = 16; else printf(" lookalike with %u bytes FIFO", com->tx_fifo_size); break; } #ifdef COM_ESP if (com->esp) { /* * Set 16550 compatibility mode. * We don't use the ESP_MODE_SCALE bit to increase the * fifo trigger levels because we can't handle large * bursts of input. * XXX flow control should be set in comparam(), not here. */ outb(com->esp_port + ESP_CMD1, ESP_SETMODE); outb(com->esp_port + ESP_CMD2, ESP_MODE_RTS | ESP_MODE_FIFO); /* Set RTS/CTS flow control. */ outb(com->esp_port + ESP_CMD1, ESP_SETFLOWTYPE); outb(com->esp_port + ESP_CMD2, ESP_FLOW_RTS); outb(com->esp_port + ESP_CMD2, ESP_FLOW_CTS); /* Set flow-control levels. */ outb(com->esp_port + ESP_CMD1, ESP_SETRXFLOW); outb(com->esp_port + ESP_CMD2, HIBYTE(768)); outb(com->esp_port + ESP_CMD2, LOBYTE(768)); outb(com->esp_port + ESP_CMD2, HIBYTE(512)); outb(com->esp_port + ESP_CMD2, LOBYTE(512)); } #endif /* COM_ESP */ sio_setreg(com, com_fifo, 0); determined_type: ; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(flags)) { device_t masterdev; com->multiport = TRUE; printf(" (multiport"); if (unit == COM_MPMASTER(flags)) printf(" master"); printf(")"); masterdev = devclass_get_device(sio_devclass, COM_MPMASTER(flags)); com->no_irq = (masterdev == NULL || bus_get_resource(masterdev, SYS_RES_IRQ, 0, NULL, NULL) != 0); } #endif /* COM_MULTIPORT */ if (unit == comconsole) printf(", console"); if (COM_IIR_TXRDYBUG(flags)) printf(" with a buggy IIR_TXRDY implementation"); printf("\n"); if (sio_fast_ih == NULL) { swi_add(&tty_ithd, "tty:sio", siopoll, NULL, SWI_TTY, 0, &sio_fast_ih); swi_add(&clk_ithd, "tty:sio", siopoll, NULL, SWI_TTY, 0, &sio_slow_ih); } minorbase = UNIT_TO_MINOR(unit); com->devs[0] = make_dev(&sio_cdevsw, minorbase, UID_ROOT, GID_WHEEL, 0600, "ttyd%r", unit); com->devs[1] = make_dev(&sio_cdevsw, minorbase | CONTROL_INIT_STATE, UID_ROOT, GID_WHEEL, 0600, "ttyid%r", unit); com->devs[2] = make_dev(&sio_cdevsw, minorbase | CONTROL_LOCK_STATE, UID_ROOT, GID_WHEEL, 0600, "ttyld%r", unit); com->devs[3] = make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK, UID_UUCP, GID_DIALER, 0660, "cuaa%r", unit); com->devs[4] = make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK | CONTROL_INIT_STATE, UID_UUCP, GID_DIALER, 0660, "cuaia%r", unit); com->devs[5] = make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK | CONTROL_LOCK_STATE, UID_UUCP, GID_DIALER, 0660, "cuala%r", unit); for (rid = 0; rid < 6; rid++) com->devs[rid]->si_drv1 = com; com->flags = flags; com->pps.ppscap = PPS_CAPTUREASSERT | PPS_CAPTURECLEAR; if (COM_PPSCTS(flags)) com->pps_bit = MSR_CTS; else com->pps_bit = MSR_DCD; pps_init(&com->pps); rid = 0; com->irqres = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (com->irqres) { ret = BUS_SETUP_INTR(device_get_parent(dev), dev, com->irqres, INTR_TYPE_TTY | INTR_FAST, siointr, com, &com->cookie); if (ret) { ret = BUS_SETUP_INTR(device_get_parent(dev), dev, com->irqres, INTR_TYPE_TTY, siointr, com, &com->cookie); if (ret == 0) device_printf(dev, "unable to activate interrupt in fast mode - using normal mode\n"); } if (ret) device_printf(dev, "could not activate interrupt\n"); #if defined(DDB) && (defined(BREAK_TO_DEBUGGER) || \ defined(ALT_BREAK_TO_DEBUGGER)) /* * Enable interrupts for early break-to-debugger support * on the console. */ if (ret == 0 && unit == comconsole) outb(siocniobase + com_ier, IER_ERXRDY | IER_ERLS | IER_EMSC); #endif } return (0); } static int sioopen(dev, flag, mode, td) dev_t dev; int flag; int mode; struct thread *td; { struct com_s *com; int error; int mynor; int s; struct tty *tp; int unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); if (com == NULL) return (ENXIO); if (com->gone) return (ENXIO); if (mynor & CONTROL_MASK) return (0); tp = dev->si_tty = com->tp = ttymalloc(com->tp); s = spltty(); /* * We jump to this label after all non-interrupted sleeps to pick * up any changes of the device state. */ open_top: while (com->state & CS_DTR_OFF) { error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "siodtr", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!com->active_out) { error = EBUSY; goto out; } } else { if (com->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&com->active_out, TTIPRI | PCATCH, "siobi", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && suser(td)) { 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 = comstart; tp->t_param = comparam; tp->t_stop = comstop; tp->t_dev = dev; tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in; (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); com->poll = com->no_irq; com->poll_output = com->loses_outints; ++com->wopeners; error = comparam(tp, &tp->t_termios); --com->wopeners; if (error != 0) goto out; /* * XXX we should goto open_top if comparam() slept. */ if (com->hasfifo) { int i; /* * (Re)enable and drain fifos. * * Certain SMC chips cause problems if the fifos * are enabled while input is ready. Turn off the * fifo if necessary to clear the input. We test * the input ready bit after enabling the fifos * since we've already enabled them in comparam() * and to handle races between enabling and fresh * input. */ for (i = 0; i < 500; i++) { sio_setreg(com, com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image); /* * XXX the delays are for superstitious * historical reasons. It must be less than * the character time at the maximum * supported speed (87 usec at 115200 bps * 8N1). Otherwise we might loop endlessly * if data is streaming in. We used to use * delays of 100. That usually worked * because DELAY(100) used to usually delay * for about 85 usec instead of 100. */ DELAY(50); if (!(inb(com->line_status_port) & LSR_RXRDY)) break; sio_setreg(com, com_fifo, 0); DELAY(50); (void) inb(com->data_port); } if (i == 500) { error = EIO; goto out; } } mtx_lock_spin(&sio_lock); (void) inb(com->line_status_port); (void) inb(com->data_port); com->prev_modem_status = com->last_modem_status = inb(com->modem_status_port); outb(com->int_ctl_port, IER_ERXRDY | IER_ERLS | IER_EMSC | (COM_IIR_TXRDYBUG(com->flags) ? 0 : IER_ETXRDY)); mtx_unlock_spin(&sio_lock); /* * Handle initial DCD. Callout devices get a fake initial * DCD (trapdoor DCD). If we are callout, then any sleeping * callin opens get woken up and resume sleeping on "siobi" * instead of "siodcd". */ /* * XXX `mynor & CALLOUT_MASK' should be * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where * TRAPDOOR_CARRIER is the default initial state for callout * devices and SOFT_CARRIER is like CLOCAL except it hides * the true carrier. */ if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) ttyld_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)) { ++com->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "siodcd", 0); if (com_addr(unit) == NULL) return (ENXIO); --com->wopeners; if (error != 0 || com->gone) goto out; goto open_top; } error = ttyld_open(tp, dev); - disc_optim(tp, &tp->t_termios, com); + com->hotchar = ttyldoptim(tp); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) com->active_out = TRUE; siosettimeout(); out: splx(s); if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); return (error); } static int sioclose(dev, flag, mode, td) dev_t dev; int flag; int mode; struct thread *td; { struct com_s *com; int mynor; int s; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (0); com = com_addr(MINOR_TO_UNIT(mynor)); if (com == NULL) return (ENODEV); tp = com->tp; s = spltty(); ttyld_close(tp, flag); - disc_optim(tp, &tp->t_termios, com); + com->hotchar = ttyldoptim(tp); comhardclose(com); ttyclose(tp); siosettimeout(); splx(s); if (com->gone) { printf("sio%d: gone\n", com->unit); s = spltty(); if (com->ibuf != NULL) free(com->ibuf, M_DEVBUF); bzero(tp, sizeof *tp); splx(s); } return (0); } static void comhardclose(com) struct com_s *com; { int s; struct tty *tp; s = spltty(); com->poll = FALSE; com->poll_output = FALSE; com->do_timestamp = FALSE; com->do_dcd_timestamp = FALSE; com->pps.ppsparam.mode = 0; sio_setreg(com, com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); tp = com->tp; #if defined(DDB) && (defined(BREAK_TO_DEBUGGER) || \ defined(ALT_BREAK_TO_DEBUGGER)) /* * Leave interrupts enabled and don't clear DTR if this is the * console. This allows us to detect break-to-debugger events * while the console device is closed. */ if (com->unit != comconsole) #endif { sio_setreg(com, com_ier, 0); if (tp->t_cflag & HUPCL /* * XXX we will miss any carrier drop between here and the * next open. Perhaps we should watch DCD even when the * port is closed; it is not sufficient to check it at * the next open because it might go up and down while * we're not watching. */ || (!com->active_out && !(com->prev_modem_status & MSR_DCD) && !(com->it_in.c_cflag & CLOCAL)) || !(tp->t_state & TS_ISOPEN)) { (void)commctl(com, TIOCM_DTR, DMBIC); if (com->dtr_wait != 0 && !(com->state & CS_DTR_OFF)) { timeout(siodtrwakeup, com, com->dtr_wait); com->state |= CS_DTR_OFF; } } } if (com->hasfifo) { /* * Disable fifos so that they are off after controlled * reboots. Some BIOSes fail to detect 16550s when the * fifos are enabled. */ sio_setreg(com, com_fifo, 0); } com->active_out = FALSE; wakeup(&com->active_out); wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } static int sioread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct com_s *com; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); com = com_addr(MINOR_TO_UNIT(mynor)); if (com == NULL || com->gone) return (ENODEV); return (ttyld_read(com->tp, uio, flag)); } static int siowrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct com_s *com; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); if (com == NULL || com->gone) return (ENODEV); /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that * is not the console. In that situation we don't need/want the X * server taking over the console. */ if (constty != NULL && unit == comconsole) constty = NULL; return (ttyld_write(com->tp, uio, flag)); } static void siobusycheck(chan) void *chan; { struct com_s *com; int s; com = (struct com_s *)chan; /* * Clear TS_BUSY if low-level output is complete. * spl locking is sufficient because siointr1() does not set CS_BUSY. * If siointr1() clears CS_BUSY after we look at it, then we'll get * called again. Reading the line status port outside of siointr1() * is safe because CS_BUSY is clear so there are no output interrupts * to lose. */ s = spltty(); if (com->state & CS_BUSY) com->extra_state &= ~CSE_BUSYCHECK; /* False alarm. */ else if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY)) { com->tp->t_state &= ~TS_BUSY; ttwwakeup(com->tp); com->extra_state &= ~CSE_BUSYCHECK; } else timeout(siobusycheck, com, hz / 100); splx(s); } static u_int siodivisor(rclk, speed) u_long rclk; speed_t speed; { long actual_speed; u_int divisor; int error; if (speed == 0) return (0); #if UINT_MAX > (ULONG_MAX - 1) / 8 if (speed > (ULONG_MAX - 1) / 8) return (0); #endif divisor = (rclk / (8UL * speed) + 1) / 2; if (divisor == 0 || divisor >= 65536) return (0); actual_speed = rclk / (16UL * divisor); /* 10 times error in percent: */ error = ((actual_speed - (long)speed) * 2000 / (long)speed + 1) / 2; /* 3.0% maximum error tolerance: */ if (error < -30 || error > 30) return (0); return (divisor); } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } /* * Call this function with the sio_lock mutex held. It will return with the * lock still held. */ static void sioinput(com) struct com_s *com; { u_char *buf; int incc; u_char line_status; int recv_data; struct tty *tp; buf = com->ibuf; tp = com->tp; if (!(tp->t_state & TS_ISOPEN) || !(tp->t_cflag & CREAD)) { com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; return; } if (tp->t_state & TS_CAN_BYPASS_L_RINT) { /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ do { /* * This may look odd, but it is using save-and-enable * semantics instead of the save-and-disable semantics * that are used everywhere else. */ mtx_unlock_spin(&sio_lock); incc = com->iptr - buf; if (tp->t_rawq.c_cc + incc > tp->t_ihiwat && (com->state & CS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); com->delta_error_counts[CE_TTY_BUF_OVERFLOW] += b_to_q((char *)buf, incc, &tp->t_rawq); buf += incc; tk_nin += incc; tk_rawcc += incc; tp->t_rawcc += incc; ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; comstart(tp); } mtx_lock_spin(&sio_lock); } while (buf < com->iptr); } else { do { /* * This may look odd, but it is using save-and-enable * semantics instead of the save-and-disable semantics * that are used everywhere else. */ mtx_unlock_spin(&sio_lock); line_status = buf[com->ierroff]; recv_data = *buf++; if (line_status & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { if (line_status & LSR_BI) recv_data |= TTY_BI; if (line_status & LSR_FE) recv_data |= TTY_FE; if (line_status & LSR_OE) recv_data |= TTY_OE; if (line_status & LSR_PE) recv_data |= TTY_PE; } ttyld_rint(tp, recv_data); mtx_lock_spin(&sio_lock); } while (buf < com->iptr); } com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; /* * There is now room for another low-level buffer full of input, * so enable RTS if it is now disabled and there is room in the * high-level buffer. */ if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } static void siointr(arg) void *arg; { struct com_s *com; #ifndef COM_MULTIPORT com = (struct com_s *)arg; mtx_lock_spin(&sio_lock); siointr1(com); mtx_unlock_spin(&sio_lock); #else /* COM_MULTIPORT */ bool_t possibly_more_intrs; int unit; /* * Loop until there is no activity on any port. This is necessary * to get an interrupt edge more than to avoid another interrupt. * If the IRQ signal is just an OR of the IRQ signals from several * devices, then the edge from one may be lost because another is * on. */ mtx_lock_spin(&sio_lock); do { possibly_more_intrs = FALSE; for (unit = 0; unit < sio_numunits; ++unit) { com = com_addr(unit); /* * XXX COM_LOCK(); * would it work here, or be counter-productive? */ if (com != NULL && !com->gone && (inb(com->int_id_port) & IIR_IMASK) != IIR_NOPEND) { siointr1(com); possibly_more_intrs = TRUE; } /* XXX COM_UNLOCK(); */ } } while (possibly_more_intrs); mtx_unlock_spin(&sio_lock); #endif /* COM_MULTIPORT */ } static struct timespec siots[8]; static int siotso; static int volatile siotsunit = -1; static int sysctl_siots(SYSCTL_HANDLER_ARGS) { char buf[128]; long long delta; size_t len; int error, i, tso; for (i = 1, tso = siotso; i < tso; i++) { delta = (long long)(siots[i].tv_sec - siots[i - 1].tv_sec) * 1000000000 + (siots[i].tv_nsec - siots[i - 1].tv_nsec); len = sprintf(buf, "%lld\n", delta); if (delta >= 110000) len += sprintf(buf + len - 1, ": *** %ld.%09ld\n", (long)siots[i].tv_sec, siots[i].tv_nsec) - 1; if (i == tso - 1) buf[len - 1] = '\0'; error = SYSCTL_OUT(req, buf, len); if (error != 0) return (error); uio_yield(); } return (0); } SYSCTL_PROC(_machdep, OID_AUTO, siots, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, sysctl_siots, "A", "sio timestamps"); static void siointr1(com) struct com_s *com; { u_char int_ctl; u_char int_ctl_new; u_char line_status; u_char modem_status; u_char *ioptr; u_char recv_data; if (COM_IIR_TXRDYBUG(com->flags)) { int_ctl = inb(com->int_ctl_port); int_ctl_new = int_ctl; } else { int_ctl = 0; int_ctl_new = 0; } while (!com->gone) { if (com->pps.ppsparam.mode & PPS_CAPTUREBOTH) { modem_status = inb(com->modem_status_port); if ((modem_status ^ com->last_modem_status) & com->pps_bit) { pps_capture(&com->pps); pps_event(&com->pps, (modem_status & com->pps_bit) ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR); } } line_status = inb(com->line_status_port); /* input event? (check first to help avoid overruns) */ while (line_status & LSR_RCV_MASK) { /* break/unnattached error bits or real input? */ if (!(line_status & LSR_RXRDY)) recv_data = 0; else recv_data = inb(com->data_port); #ifdef DDB #ifdef ALT_BREAK_TO_DEBUGGER if (com->unit == comconsole && db_alt_break(recv_data, &com->alt_brk_state) != 0) breakpoint(); #endif /* ALT_BREAK_TO_DEBUGGER */ #endif /* DDB */ if (line_status & (LSR_BI | LSR_FE | LSR_PE)) { /* * Don't store BI if IGNBRK or FE/PE if IGNPAR. * Otherwise, push the work to a higher level * (to handle PARMRK) if we're bypassing. * Otherwise, convert BI/FE and PE+INPCK to 0. * * This makes bypassing work right in the * usual "raw" case (IGNBRK set, and IGNPAR * and INPCK clear). * * Note: BI together with FE/PE means just BI. */ if (line_status & LSR_BI) { #if defined(DDB) && defined(BREAK_TO_DEBUGGER) if (com->unit == comconsole) { breakpoint(); goto cont; } #endif if (com->tp == NULL || com->tp->t_iflag & IGNBRK) goto cont; } else { if (com->tp == NULL || com->tp->t_iflag & IGNPAR) goto cont; } if (com->tp->t_state & TS_CAN_BYPASS_L_RINT && (line_status & (LSR_BI | LSR_FE) || com->tp->t_iflag & INPCK)) recv_data = 0; } ++com->bytes_in; if (com->hotchar != 0 && recv_data == com->hotchar) swi_sched(sio_fast_ih, 0); ioptr = com->iptr; if (ioptr >= com->ibufend) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { if (com->do_timestamp) microtime(&com->timestamp); ++com_events; swi_sched(sio_slow_ih, SWI_DELAY); #if 0 /* for testing input latency vs efficiency */ if (com->iptr - com->ibuf == 8) swi_sched(sio_fast_ih, 0); #endif ioptr[0] = recv_data; ioptr[com->ierroff] = line_status; com->iptr = ++ioptr; if (ioptr == com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); if (line_status & LSR_OE) CE_RECORD(com, CE_OVERRUN); } cont: if (line_status & LSR_TXRDY && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) goto txrdy; /* * "& 0x7F" is to avoid the gcc-1.40 generating a slow * jump from the top of the loop to here */ line_status = inb(com->line_status_port) & 0x7F; } /* modem status change? (always check before doing output) */ modem_status = inb(com->modem_status_port); if (modem_status != com->last_modem_status) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * Schedule high level to handle DCD changes. Note * that we don't use the delta bits anywhere. Some * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; swi_sched(sio_fast_ih, 0); } /* handle CTS change immediately for crisp flow ctl */ if (com->state & CS_CTS_OFLOW) { if (modem_status & MSR_CTS) com->state |= CS_ODEVREADY; else com->state &= ~CS_ODEVREADY; } } txrdy: /* output queued and everything ready? */ if (line_status & LSR_TXRDY && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { ioptr = com->obufq.l_head; if (com->tx_fifo_size > 1 && com->unit != siotsunit) { u_int ocount; ocount = com->obufq.l_tail - ioptr; if (ocount > com->tx_fifo_size) ocount = com->tx_fifo_size; com->bytes_out += ocount; do outb(com->data_port, *ioptr++); while (--ocount != 0); } else { outb(com->data_port, *ioptr++); ++com->bytes_out; if (com->unit == siotsunit && siotso < sizeof siots / sizeof siots[0]) nanouptime(&siots[siotso++]); } com->obufq.l_head = ioptr; if (COM_IIR_TXRDYBUG(com->flags)) int_ctl_new = int_ctl | IER_ETXRDY; if (ioptr >= com->obufq.l_tail) { struct lbq *qp; qp = com->obufq.l_next; qp->l_queued = FALSE; qp = qp->l_next; if (qp != NULL) { com->obufq.l_head = qp->l_head; com->obufq.l_tail = qp->l_tail; com->obufq.l_next = qp; } else { /* output just completed */ if (COM_IIR_TXRDYBUG(com->flags)) int_ctl_new = int_ctl & ~IER_ETXRDY; com->state &= ~CS_BUSY; } if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; /* handle at high level ASAP */ swi_sched(sio_fast_ih, 0); } } if (COM_IIR_TXRDYBUG(com->flags) && int_ctl != int_ctl_new) outb(com->int_ctl_port, int_ctl_new); } /* finished? */ #ifndef COM_MULTIPORT if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) #endif /* COM_MULTIPORT */ return; } } static int sioioctl(dev, cmd, data, flag, td) dev_t dev; u_long cmd; caddr_t data; int flag; struct thread *td; { struct com_s *com; int error; int mynor; int s; struct tty *tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) u_long oldcmd; struct termios term; #endif mynor = minor(dev); com = com_addr(MINOR_TO_UNIT(mynor)); if (com == NULL || com->gone) return (ENODEV); if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->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); default: return (ENOTTY); } } tp = com->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 *)data; struct termios *lt = mynor & CALLOUT_MASK ? &com->lt_out : &com->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); 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 = ttyioctl(dev, cmd, data, flag, td); - disc_optim(tp, &tp->t_termios, com); + com->hotchar = ttyldoptim(tp); if (error != ENOTTY) return (error); s = spltty(); switch (cmd) { case TIOCSBRK: sio_setreg(com, com_cfcr, com->cfcr_image |= CFCR_SBREAK); break; case TIOCCBRK: sio_setreg(com, com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); break; case TIOCSDTR: (void)commctl(com, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void)commctl(com, TIOCM_DTR, DMBIC); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: (void)commctl(com, *(int *)data, DMSET); break; case TIOCMBIS: (void)commctl(com, *(int *)data, DMBIS); break; case TIOCMBIC: (void)commctl(com, *(int *)data, DMBIC); break; case TIOCMGET: *(int *)data = commctl(com, 0, DMGET); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(td); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); error = pps_ioctl(cmd, data, &com->pps); if (error == ENODEV) error = ENOTTY; return (error); } splx(s); return (0); } /* software interrupt handler for SWI_TTY */ static void siopoll(void *dummy) { int unit; if (com_events == 0) return; repeat: for (unit = 0; unit < sio_numunits; ++unit) { struct com_s *com; int incc; struct tty *tp; com = com_addr(unit); if (com == NULL) continue; tp = com->tp; if (tp == NULL || com->gone) { /* * Discard any events related to never-opened or * going-away devices. */ mtx_lock_spin(&sio_lock); incc = com->iptr - com->ibuf; com->iptr = com->ibuf; if (com->state & CS_CHECKMSR) { incc += LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; } com_events -= incc; mtx_unlock_spin(&sio_lock); continue; } if (com->iptr != com->ibuf) { mtx_lock_spin(&sio_lock); sioinput(com); mtx_unlock_spin(&sio_lock); } if (com->state & CS_CHECKMSR) { u_char delta_modem_status; mtx_lock_spin(&sio_lock); delta_modem_status = com->last_modem_status ^ com->prev_modem_status; com->prev_modem_status = com->last_modem_status; com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; mtx_unlock_spin(&sio_lock); if (delta_modem_status & MSR_DCD) ttyld_modem(tp, com->prev_modem_status & MSR_DCD); } if (com->state & CS_ODONE) { mtx_lock_spin(&sio_lock); com_events -= LOTS_OF_EVENTS; com->state &= ~CS_ODONE; mtx_unlock_spin(&sio_lock); if (!(com->state & CS_BUSY) && !(com->extra_state & CSE_BUSYCHECK)) { timeout(siobusycheck, com, hz / 100); com->extra_state |= CSE_BUSYCHECK; } ttyld_start(tp); } if (com_events == 0) break; } if (com_events >= LOTS_OF_EVENTS) goto repeat; } static int comparam(tp, t) struct tty *tp; struct termios *t; { u_int cfcr; int cflag; struct com_s *com; u_int divisor; u_char dlbh; u_char dlbl; u_char efr_flowbits; int s; int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); if (com == NULL) return (ENODEV); /* check requested parameters */ if (t->c_ispeed != (t->c_ospeed != 0 ? t->c_ospeed : tp->t_ospeed)) return (EINVAL); divisor = siodivisor(com->rclk, t->c_ispeed); if (divisor == 0) return (EINVAL); /* parameters are OK, convert them to the com struct and the device */ s = spltty(); if (t->c_ospeed == 0) (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ else (void)commctl(com, TIOCM_DTR, DMBIS); cflag = t->c_cflag; switch (cflag & CSIZE) { case CS5: cfcr = CFCR_5BITS; break; case CS6: cfcr = CFCR_6BITS; break; case CS7: cfcr = CFCR_7BITS; break; default: cfcr = CFCR_8BITS; break; } if (cflag & PARENB) { cfcr |= CFCR_PENAB; if (!(cflag & PARODD)) cfcr |= CFCR_PEVEN; } if (cflag & CSTOPB) cfcr |= CFCR_STOPB; if (com->hasfifo) { /* * Use a fifo trigger level low enough so that the input * latency from the fifo is less than about 16 msec and * the total latency is less than about 30 msec. These * latencies are reasonable for humans. Serial comms * protocols shouldn't expect anything better since modem * latencies are larger. * * The fifo trigger level cannot be set at RX_HIGH for high * speed connections without further work on reducing * interrupt disablement times in other parts of the system, * without producing silo overflow errors. */ com->fifo_image = com->unit == siotsunit ? 0 : t->c_ispeed <= 4800 ? FIFO_ENABLE : FIFO_ENABLE | FIFO_RX_MEDH; #ifdef COM_ESP /* * The Hayes ESP card needs the fifo DMA mode bit set * in compatibility mode. If not, it will interrupt * for each character received. */ if (com->esp) com->fifo_image |= FIFO_DMA_MODE; #endif sio_setreg(com, com_fifo, com->fifo_image); } /* * This returns with interrupts disabled so that we can complete * the speed change atomically. Keeping interrupts disabled is * especially important while com_data is hidden. */ (void) siosetwater(com, t->c_ispeed); sio_setreg(com, com_cfcr, cfcr | CFCR_DLAB); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (UMC8669F), setting them while input * is arriving loses sync until data stops arriving. */ dlbl = divisor & 0xFF; if (sio_getreg(com, com_dlbl) != dlbl) sio_setreg(com, com_dlbl, dlbl); dlbh = divisor >> 8; if (sio_getreg(com, com_dlbh) != dlbh) sio_setreg(com, com_dlbh, dlbh); efr_flowbits = 0; if (cflag & CRTS_IFLOW) { com->state |= CS_RTS_IFLOW; efr_flowbits |= EFR_AUTORTS; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state |= CS_ODEVREADY; com->state &= ~CS_CTS_OFLOW; if (cflag & CCTS_OFLOW) { com->state |= CS_CTS_OFLOW; efr_flowbits |= EFR_AUTOCTS; if (!(com->last_modem_status & MSR_CTS)) com->state &= ~CS_ODEVREADY; } if (com->st16650a) { sio_setreg(com, com_lcr, LCR_EFR_ENABLE); sio_setreg(com, com_efr, (sio_getreg(com, com_efr) & ~(EFR_AUTOCTS | EFR_AUTORTS)) | efr_flowbits); } sio_setreg(com, com_cfcr, com->cfcr_image = cfcr); /* XXX shouldn't call functions while intrs are disabled. */ - disc_optim(tp, t, com); + com->hotchar = ttyldoptim(tp); mtx_unlock_spin(&sio_lock); splx(s); comstart(tp); if (com->ibufold != NULL) { free(com->ibufold, M_DEVBUF); com->ibufold = NULL; } return (0); } /* * This function must be called with the sio_lock mutex released and will * return with it obtained. */ static int siosetwater(com, speed) struct com_s *com; speed_t speed; { int cp4ticks; u_char *ibuf; int ibufsize; struct tty *tp; /* * Make the buffer size large enough to handle a softtty interrupt * latency of about 2 ticks without loss of throughput or data * (about 3 ticks if input flow control is not used or not honoured, * but a bit less for CS5-CS7 modes). */ cp4ticks = speed / 10 / hz * 4; for (ibufsize = 128; ibufsize < cp4ticks;) ibufsize <<= 1; if (ibufsize == com->ibufsize) { mtx_lock_spin(&sio_lock); return (0); } /* * Allocate input buffer. The extra factor of 2 in the size is * to allow for an error byte for each input byte. */ ibuf = malloc(2 * ibufsize, M_DEVBUF, M_NOWAIT); if (ibuf == NULL) { mtx_lock_spin(&sio_lock); return (ENOMEM); } /* Initialize non-critical variables. */ com->ibufold = com->ibuf; com->ibufsize = ibufsize; tp = com->tp; if (tp != NULL) { tp->t_ififosize = 2 * ibufsize; tp->t_ispeedwat = (speed_t)-1; tp->t_ospeedwat = (speed_t)-1; } /* * Read current input buffer, if any. Continue with interrupts * disabled. */ mtx_lock_spin(&sio_lock); if (com->iptr != com->ibuf) sioinput(com); /*- * Initialize critical variables, including input buffer watermarks. * The external device is asked to stop sending when the buffer * exactly reaches high water, or when the high level requests it. * The high level is notified immediately (rather than at a later * clock tick) when this watermark is reached. * The buffer size is chosen so the watermark should almost never * be reached. * The low watermark is invisibly 0 since the buffer is always * emptied all at once. */ com->iptr = com->ibuf = ibuf; com->ibufend = ibuf + ibufsize; com->ierroff = ibufsize; com->ihighwater = ibuf + 3 * ibufsize / 4; return (0); } static void comstart(tp) struct tty *tp; { struct com_s *com; int s; int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); if (com == NULL) return; s = spltty(); mtx_lock_spin(&sio_lock); if (tp->t_state & TS_TTSTOP) com->state &= ~CS_TTGO; else com->state |= CS_TTGO; if (tp->t_state & TS_TBLOCK) { if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); } else { if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } mtx_unlock_spin(&sio_lock); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(s); return; } if (tp->t_outq.c_cc != 0) { struct lbq *qp; struct lbq *next; if (!com->obufs[0].l_queued) { com->obufs[0].l_tail = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, sizeof com->obuf1); com->obufs[0].l_next = NULL; com->obufs[0].l_queued = TRUE; mtx_lock_spin(&sio_lock); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[0]; } else { com->obufq.l_head = com->obufs[0].l_head; com->obufq.l_tail = com->obufs[0].l_tail; com->obufq.l_next = &com->obufs[0]; com->state |= CS_BUSY; } mtx_unlock_spin(&sio_lock); } if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { com->obufs[1].l_tail = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, sizeof com->obuf2); com->obufs[1].l_next = NULL; com->obufs[1].l_queued = TRUE; mtx_lock_spin(&sio_lock); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[1]; } else { com->obufq.l_head = com->obufs[1].l_head; com->obufq.l_tail = com->obufs[1].l_tail; com->obufq.l_next = &com->obufs[1]; com->state |= CS_BUSY; } mtx_unlock_spin(&sio_lock); } tp->t_state |= TS_BUSY; } mtx_lock_spin(&sio_lock); if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); /* fake interrupt to start output */ mtx_unlock_spin(&sio_lock); ttwwakeup(tp); splx(s); } static void comstop(tp, rw) struct tty *tp; int rw; { struct com_s *com; com = com_addr(DEV_TO_UNIT(tp->t_dev)); if (com == NULL || com->gone) return; mtx_lock_spin(&sio_lock); if (rw & FWRITE) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif sio_setreg(com, com_fifo, FIFO_XMT_RST | com->fifo_image); com->obufs[0].l_queued = FALSE; com->obufs[1].l_queued = FALSE; if (com->state & CS_ODONE) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); com->tp->t_state &= ~TS_BUSY; } if (rw & FREAD) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif sio_setreg(com, com_fifo, FIFO_RCV_RST | com->fifo_image); com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } mtx_unlock_spin(&sio_lock); comstart(tp); } static int commctl(com, bits, how) struct com_s *com; int bits; int how; { int mcr; int msr; if (how == DMGET) { bits = TIOCM_LE; /* XXX - always enabled while open */ mcr = com->mcr_image; if (mcr & MCR_DTR) bits |= TIOCM_DTR; if (mcr & MCR_RTS) bits |= TIOCM_RTS; msr = com->prev_modem_status; if (msr & MSR_CTS) bits |= TIOCM_CTS; if (msr & MSR_DCD) bits |= TIOCM_CD; if (msr & MSR_DSR) bits |= TIOCM_DSR; /* * XXX - MSR_RI is naturally volatile, and we make MSR_TERI * more volatile by reading the modem status a lot. Perhaps * we should latch both bits until the status is read here. */ if (msr & (MSR_RI | MSR_TERI)) bits |= TIOCM_RI; return (bits); } mcr = 0; if (bits & TIOCM_DTR) mcr |= MCR_DTR; if (bits & TIOCM_RTS) mcr |= MCR_RTS; if (com->gone) return(0); mtx_lock_spin(&sio_lock); switch (how) { case DMSET: outb(com->modem_ctl_port, com->mcr_image = mcr | (com->mcr_image & MCR_IENABLE)); break; case DMBIS: outb(com->modem_ctl_port, com->mcr_image |= mcr); break; case DMBIC: outb(com->modem_ctl_port, com->mcr_image &= ~mcr); break; } mtx_unlock_spin(&sio_lock); return (0); } static void siosettimeout() { struct com_s *com; bool_t someopen; int unit; /* * Set our timeout period to 1 second if no polled devices are open. * Otherwise set it to max(1/200, 1/hz). * Enable timeouts iff some device is open. */ untimeout(comwakeup, (void *)NULL, sio_timeout_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < sio_numunits; ++unit) { com = com_addr(unit); if (com != NULL && com->tp != NULL && com->tp->t_state & TS_ISOPEN && !com->gone) { someopen = TRUE; if (com->poll || com->poll_output) { sio_timeout = hz > 200 ? hz / 200 : 1; break; } } } if (someopen) { sio_timeouts_until_log = hz / sio_timeout; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < sio_numunits; ++unit) { com = com_addr(unit); if (com != NULL && !com->gone && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { mtx_lock_spin(&sio_lock); siointr1(com); mtx_unlock_spin(&sio_lock); } } /* * Check for and log errors, but not too often. */ if (--sio_timeouts_until_log > 0) return; sio_timeouts_until_log = hz / sio_timeout; for (unit = 0; unit < sio_numunits; ++unit) { int errnum; com = com_addr(unit); if (com == NULL) continue; if (com->gone) continue; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { u_int delta; u_long total; mtx_lock_spin(&sio_lock); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; mtx_unlock_spin(&sio_lock); if (delta == 0) continue; total = com->error_counts[errnum] += delta; log(LOG_ERR, "sio%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); } } } -static void -disc_optim(tp, t, com) - struct tty *tp; - struct termios *t; - struct com_s *com; -{ - if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) - && (!(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; - com->hotchar = linesw[tp->t_line].l_hotchar; -} - /* * Following are all routines needed for SIO to act as console */ struct siocnstate { u_char dlbl; u_char dlbh; u_char ier; u_char cfcr; u_char mcr; }; /* * This is a function in order to not replicate "ttyd%d" more * places than absolutely necessary. */ static void siocnset(struct consdev *cd, int unit) { cd->cn_unit = unit; sprintf(cd->cn_name, "ttyd%d", unit); } #ifndef __alpha__ static speed_t siocngetspeed(Port_t, u_long rclk); #endif static void siocnclose(struct siocnstate *sp, Port_t iobase); static void siocnopen(struct siocnstate *sp, Port_t iobase, int speed); static void siocntxwait(Port_t iobase); #ifdef __alpha__ int siocnattach(int port, int speed); int siogdbattach(int port, int speed); int siogdbgetc(void); void siogdbputc(int c); #else static cn_probe_t siocnprobe; static cn_init_t siocninit; static cn_term_t siocnterm; #endif static cn_checkc_t siocncheckc; static cn_getc_t siocngetc; static cn_putc_t siocnputc; #ifndef __alpha__ CONS_DRIVER(sio, siocnprobe, siocninit, siocnterm, siocngetc, siocncheckc, siocnputc, NULL); #endif #if DDB > 0 static struct consdev gdbconsdev; #endif static void siocntxwait(iobase) Port_t iobase; { int timo; /* * Wait for any pending transmission to finish. Required to avoid * the UART lockup bug when the speed is changed, and for normal * transmits. */ timo = 100000; while ((inb(iobase + com_lsr) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY) && --timo != 0) ; } #ifndef __alpha__ /* * Read the serial port specified and try to figure out what speed * it's currently running at. We're assuming the serial port has * been initialized and is basicly idle. This routine is only intended * to be run at system startup. * * If the value read from the serial port doesn't make sense, return 0. */ static speed_t siocngetspeed(iobase, rclk) Port_t iobase; u_long rclk; { u_int divisor; u_char dlbh; u_char dlbl; u_char cfcr; cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); dlbl = inb(iobase + com_dlbl); dlbh = inb(iobase + com_dlbh); outb(iobase + com_cfcr, cfcr); divisor = dlbh << 8 | dlbl; /* XXX there should be more sanity checking. */ if (divisor == 0) return (CONSPEED); return (rclk / (16UL * divisor)); } #endif static void siocnopen(sp, iobase, speed) struct siocnstate *sp; Port_t iobase; int speed; { u_int divisor; u_char dlbh; u_char dlbl; /* * Save all the device control registers except the fifo register * and set our default ones (cs8 -parenb speed=comdefaultrate). * We can't save the fifo register since it is read-only. */ sp->ier = inb(iobase + com_ier); outb(iobase + com_ier, 0); /* spltty() doesn't stop siointr() */ siocntxwait(iobase); sp->cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); sp->dlbl = inb(iobase + com_dlbl); sp->dlbh = inb(iobase + com_dlbh); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (Startech), setting them clears the * data input register. This also reduces the effects of the * UMC8669F bug. */ divisor = siodivisor(comdefaultrclk, speed); dlbl = divisor & 0xFF; if (sp->dlbl != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = divisor >> 8; if (sp->dlbh != dlbh) outb(iobase + com_dlbh, dlbh); outb(iobase + com_cfcr, CFCR_8BITS); sp->mcr = inb(iobase + com_mcr); /* * We don't want interrupts, but must be careful not to "disable" * them by clearing the MCR_IENABLE bit, since that might cause * an interrupt by floating the IRQ line. */ outb(iobase + com_mcr, (sp->mcr & MCR_IENABLE) | MCR_DTR | MCR_RTS); } static void siocnclose(sp, iobase) struct siocnstate *sp; Port_t iobase; { /* * Restore the device control registers. */ siocntxwait(iobase); outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); if (sp->dlbl != inb(iobase + com_dlbl)) outb(iobase + com_dlbl, sp->dlbl); if (sp->dlbh != inb(iobase + com_dlbh)) outb(iobase + com_dlbh, sp->dlbh); outb(iobase + com_cfcr, sp->cfcr); /* * XXX damp oscillations of MCR_DTR and MCR_RTS by not restoring them. */ outb(iobase + com_mcr, sp->mcr | MCR_DTR | MCR_RTS); outb(iobase + com_ier, sp->ier); } #ifndef __alpha__ static void siocnprobe(cp) struct consdev *cp; { speed_t boot_speed; u_char cfcr; u_int divisor; int s, unit; struct siocnstate sp; /* * Find our first enabled console, if any. If it is a high-level * console device, then initialize it and return successfully. * If it is a low-level console device, then initialize it and * return unsuccessfully. It must be initialized in both cases * for early use by console drivers and debuggers. Initializing * the hardware is not necessary in all cases, since the i/o * routines initialize it on the fly, but it is necessary if * input might arrive while the hardware is switched back to an * uninitialized state. We can't handle multiple console devices * yet because our low-level routines don't take a device arg. * We trust the user to set the console flags properly so that we * don't need to probe. */ cp->cn_pri = CN_DEAD; for (unit = 0; unit < 16; unit++) { /* XXX need to know how many */ int flags; if (resource_disabled("sio", unit)) continue; if (resource_int_value("sio", unit, "flags", &flags)) continue; if (COM_CONSOLE(flags) || COM_DEBUGGER(flags)) { int port; Port_t iobase; if (resource_int_value("sio", unit, "port", &port)) continue; iobase = port; s = spltty(); if (boothowto & RB_SERIAL) { boot_speed = siocngetspeed(iobase, comdefaultrclk); if (boot_speed) comdefaultrate = boot_speed; } /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); divisor = siodivisor(comdefaultrclk, comdefaultrate); outb(iobase + com_dlbl, divisor & 0xff); outb(iobase + com_dlbh, divisor >> 8); outb(iobase + com_cfcr, cfcr); siocnopen(&sp, iobase, comdefaultrate); splx(s); if (COM_CONSOLE(flags) && !COM_LLCONSOLE(flags)) { siocnset(cp, unit); cp->cn_pri = COM_FORCECONSOLE(flags) || boothowto & RB_SERIAL ? CN_REMOTE : CN_NORMAL; siocniobase = iobase; siocnunit = unit; } if (COM_DEBUGGER(flags)) { printf("sio%d: gdb debugging port\n", unit); siogdbiobase = iobase; siogdbunit = unit; #if DDB > 0 siocnset(&gdbconsdev, unit); gdb_arg = &gdbconsdev; gdb_getc = siocngetc; gdb_putc = siocnputc; #endif } } } #ifdef __i386__ #if DDB > 0 /* * XXX Ugly Compatability. * If no gdb port has been specified, set it to be the console * as some configuration files don't specify the gdb port. */ if (gdb_arg == NULL && (boothowto & RB_GDB)) { printf("Warning: no GDB port specified. Defaulting to sio%d.\n", siocnunit); printf("Set flag 0x80 on desired GDB port in your\n"); printf("configuration file (currently sio only).\n"); siogdbiobase = siocniobase; siogdbunit = siocnunit; siocnset(&gdbconsdev, siocnunit); gdb_arg = &gdbconsdev; gdb_getc = siocngetc; gdb_putc = siocnputc; } #endif #endif } static void siocninit(cp) struct consdev *cp; { comconsole = cp->cn_unit; } static void siocnterm(cp) struct consdev *cp; { comconsole = -1; } #endif #ifdef __alpha__ CONS_DRIVER(sio, NULL, NULL, NULL, siocngetc, siocncheckc, siocnputc, NULL); int siocnattach(port, speed) int port; int speed; { int s; u_char cfcr; u_int divisor; struct siocnstate sp; int unit = 0; /* XXX random value! */ siocniobase = port; siocnunit = unit; comdefaultrate = speed; sio_consdev.cn_pri = CN_NORMAL; siocnset(&sio_consdev, unit); s = spltty(); /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(siocniobase + com_cfcr); outb(siocniobase + com_cfcr, CFCR_DLAB | cfcr); divisor = siodivisor(comdefaultrclk, comdefaultrate); outb(siocniobase + com_dlbl, divisor & 0xff); outb(siocniobase + com_dlbh, divisor >> 8); outb(siocniobase + com_cfcr, cfcr); siocnopen(&sp, siocniobase, comdefaultrate); splx(s); cnadd(&sio_consdev); return (0); } int siogdbattach(port, speed) int port; int speed; { int s; u_char cfcr; u_int divisor; struct siocnstate sp; int unit = 1; /* XXX random value! */ siogdbiobase = port; gdbdefaultrate = speed; printf("sio%d: gdb debugging port\n", unit); siogdbunit = unit; #if DDB > 0 siocnset(&gdbconsdev, unit); gdb_arg = &gdbconsdev; gdb_getc = siocngetc; gdb_putc = siocnputc; #endif s = spltty(); /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(siogdbiobase + com_cfcr); outb(siogdbiobase + com_cfcr, CFCR_DLAB | cfcr); divisor = siodivisor(comdefaultrclk, gdbdefaultrate); outb(siogdbiobase + com_dlbl, divisor & 0xff); outb(siogdbiobase + com_dlbh, divisor >> 8); outb(siogdbiobase + com_cfcr, cfcr); siocnopen(&sp, siogdbiobase, gdbdefaultrate); splx(s); return (0); } #endif static int siocncheckc(struct consdev *cd) { int c; Port_t iobase; int s; struct siocnstate sp; speed_t speed; if (cd->cn_unit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); if (inb(iobase + com_lsr) & LSR_RXRDY) c = inb(iobase + com_data); else c = -1; siocnclose(&sp, iobase); splx(s); return (c); } static int siocngetc(struct consdev *cd) { int c; Port_t iobase; int s; struct siocnstate sp; speed_t speed; if (cd->cn_unit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); while (!(inb(iobase + com_lsr) & LSR_RXRDY)) ; c = inb(iobase + com_data); siocnclose(&sp, iobase); splx(s); return (c); } static void siocnputc(struct consdev *cd, int c) { int need_unlock; int s; struct siocnstate sp; Port_t iobase; speed_t speed; if (cd->cn_unit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); need_unlock = 0; if (sio_inited == 2 && !mtx_owned(&sio_lock)) { mtx_lock_spin(&sio_lock); need_unlock = 1; } siocnopen(&sp, iobase, speed); siocntxwait(iobase); outb(iobase + com_data, c); siocnclose(&sp, iobase); if (need_unlock) mtx_unlock_spin(&sio_lock); splx(s); } #ifdef __alpha__ int siogdbgetc() { int c; Port_t iobase; speed_t speed; int s; struct siocnstate sp; if (siogdbunit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); while (!(inb(iobase + com_lsr) & LSR_RXRDY)) ; c = inb(iobase + com_data); siocnclose(&sp, iobase); splx(s); return (c); } void siogdbputc(c) int c; { Port_t iobase; speed_t speed; int s; struct siocnstate sp; if (siogdbunit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); siocntxwait(siogdbiobase); outb(siogdbiobase + com_data, c); siocnclose(&sp, siogdbiobase); splx(s); } #endif diff --git a/sys/dev/sx/sx.c b/sys/dev/sx/sx.c index 68266bd6382d..ae50e32033ca 100644 --- a/sys/dev/sx/sx.c +++ b/sys/dev/sx/sx.c @@ -1,2018 +1,1992 @@ /* * Device tsfsdriver for Specialix I/O8+ multiport serial card. * * Copyright 2003 Frank Mayhar * * Derived from the "si" driver by Peter Wemm , using * lots of information from the Linux "specialix" driver by Roger Wolff * and from the Intel CD1865 "Intelligent Eight- * Channel Communications Controller" datasheet. Roger was also nice * enough to answer numerous questions about stuff specific to the I/O8+ * not covered by the CD1865 datasheet. * * 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 * notices, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notices, this list of conditions and the foljxowing disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE. * * $FreeBSD$ */ /* Main tty driver routines for the Specialix I/O8+ device driver. */ #include "opt_compat.h" #include "opt_debug_sx.h" #include #include #if defined(COMPAT_43) || defined(COMPAT_SUNOS) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SX_BROKEN_CTS enum sx_mctl { GET, SET, BIS, BIC }; static int sx_modem(struct sx_softc *, struct sx_port *, enum sx_mctl, int); static void sx_write_enable(struct sx_port *, int); static void sx_start(struct tty *); static void sx_stop(struct tty *, int); -static void sx_disc_optim(struct tty *tp, struct termios *t,struct sx_port *pp); static void sxhardclose(struct sx_port *pp); static void sxdtrwakeup(void *chan); static void sx_shutdown_chan(struct sx_port *); #ifdef SX_DEBUG static char *sx_mctl2str(enum sx_mctl cmd); #endif static int sxparam(struct tty *, struct termios *); static void sx_modem_state(struct sx_softc *sc, struct sx_port *pp, int card); static d_open_t sxopen; static d_close_t sxclose; static d_write_t sxwrite; static d_ioctl_t sxioctl; #define CDEV_MAJOR 185 static struct cdevsw sx_cdevsw = { .d_version = D_VERSION, .d_open = sxopen, .d_close = sxclose, .d_write = sxwrite, .d_ioctl = sxioctl, .d_name = "sx", .d_flags = D_TTY | D_NEEDGIANT, }; static int sx_debug = 0; /* DBG_ALL|DBG_PRINTF|DBG_MODEM|DBG_IOCTL|DBG_PARAM;e */ SYSCTL_INT(_machdep, OID_AUTO, sx_debug, CTLFLAG_RW, &sx_debug, 0, ""); static struct tty *sx__tty; static int sx_numunits; devclass_t sx_devclass; /* * See sx.h for these values. */ static struct speedtab bdrates[] = { { B75, CLK75, }, { B110, CLK110, }, { B150, CLK150, }, { B300, CLK300, }, { B600, CLK600, }, { B1200, CLK1200, }, { B2400, CLK2400, }, { B4800, CLK4800, }, { B9600, CLK9600, }, { B19200, CLK19200, }, { B38400, CLK38400, }, { B57600, CLK57600, }, { B115200, CLK115200, }, { -1, -1 }, }; /* * Approximate (rounded) character per second rates. Translated at card * initialization time to characters per clock tick. */ static int done_chartimes = 0; static struct speedtab chartimes[] = { { B75, 8, }, { B110, 11, }, { B150, 15, }, { B300, 30, }, { B600, 60, }, { B1200, 120, }, { B2400, 240, }, { B4800, 480, }, { B9600, 960, }, { B19200, 1920, }, { B38400, 3840, }, { B57600, 5760, }, { B115200, 11520, }, { -1, -1 }, }; static volatile int in_interrupt = 0; /* Inside interrupt handler? */ static int sx_flags; /* The flags we were configured with. */ SYSCTL_INT(_machdep, OID_AUTO, sx_flags, CTLFLAG_RW, &sx_flags, 0, ""); #ifdef POLL static int sx_pollrate; /* in addition to irq */ static int sx_realpoll = 0; /* poll HW on timer */ SYSCTL_INT(_machdep, OID_AUTO, sx_pollrate, CTLFLAG_RW, &sx_pollrate, 0, ""); SYSCTL_INT(_machdep, OID_AUTO, sx_realpoll, CTLFLAG_RW, &sx_realpoll, 0, ""); static int init_finished = 0; static void sx_poll(void *); #endif /* * sxattach() * Initialize and attach the card, initialize the driver. * * Description: * This is the standard attach routine. It initializes the I/O8+ * card, identifies the chip on that card, then allocates and * initializes the various data structures used by the driver * itself. */ int sxattach( device_t dev) { int unit; struct sx_softc *sc; struct tty *tp; struct speedtab *spt; int chip, x, y; char rev; int error; sc = device_get_softc(dev); unit = device_get_unit(dev); sx_flags = device_get_flags(dev); if (sx_numunits < unit + 1) sx_numunits = unit + 1; DPRINT((0, DBG_AUTOBOOT, "sx%d: sxattach\n", unit)); /* Reset the CD1865. */ if ((error = sx_init_cd1865(sc, unit)) != 0) { return(error); } /* * ID the chip: * * Chip revcode pkgtype * GFRCR SRCR bit 7 * CD180 rev B 0x81 0 * CD180 rev C 0x82 0 * CD1864 rev A 0x82 1 * CD1865 rev A 0x83 1 -- Do not use!!! Does not work. * CD1865 rev B 0x84 1 * -- Thanks to Gwen Wang, Cirrus Logic (via Roger Wollf). */ switch (sx_cd1865_in(sc, CD1865_GFRCR)) { case 0x82: chip = 1864; rev = 'A'; break; case 0x83: chip = 1865; rev = 'A'; break; case 0x84: chip = 1865; rev = 'B'; break; case 0x85: chip = 1865; rev = 'C'; break; default: chip = -1; rev = '\0'; break; } if (bootverbose && chip != -1) printf("sx%d: Specialix I/O8+ CD%d processor rev %c\n", unit, chip, rev); DPRINT((0, DBG_AUTOBOOT, "sx%d: GFRCR 0x%02x\n", unit, sx_cd1865_in(sc, CD1865_GFRCR))); #ifdef POLL if (sx_pollrate == 0) { sx_pollrate = POLLHZ; /* in addition to irq */ #ifdef REALPOLL sx_realpoll = 1; /* scan always */ #endif } #endif sc->sc_ports = (struct sx_port *)malloc( sizeof(struct sx_port) * SX_NUMCHANS, M_DEVBUF, M_NOWAIT); if (sc->sc_ports == NULL) { printf("sx%d: No memory for sx_port structs!\n", unit); return(EINVAL); } bzero(sc->sc_ports, sizeof(struct sx_port) * SX_NUMCHANS); /* * Allocate tty structures for the channels. */ tp = (struct tty *)malloc(sizeof(struct tty) * SX_NUMCHANS, M_DEVBUF, M_NOWAIT); if (tp == NULL) { free(sc->sc_ports, M_DEVBUF); printf("sx%d: No memory for tty structs!\n", unit); return(EINVAL); } bzero(tp, sizeof(struct tty) * SX_NUMCHANS); sx__tty = tp; /* * Initialize the channels. */ for (x = 0; x < SX_NUMCHANS; x++) { sc->sc_ports[x].sp_chan = x; sc->sc_ports[x].sp_tty = tp++; sc->sc_ports[x].sp_state = 0; /* internal flag */ sc->sc_ports[x].sp_dtr_wait = 3 * hz; sc->sc_ports[x].sp_iin.c_iflag = TTYDEF_IFLAG; sc->sc_ports[x].sp_iin.c_oflag = TTYDEF_OFLAG; sc->sc_ports[x].sp_iin.c_cflag = TTYDEF_CFLAG; sc->sc_ports[x].sp_iin.c_lflag = TTYDEF_LFLAG; termioschars(&sc->sc_ports[x].sp_iin); sc->sc_ports[x].sp_iin.c_ispeed = TTYDEF_SPEED;; sc->sc_ports[x].sp_iin.c_ospeed = TTYDEF_SPEED;; sc->sc_ports[x].sp_iout = sc->sc_ports[x].sp_iin; } if (done_chartimes == 0) { for (spt = chartimes ; spt->sp_speed != -1; spt++) { if ((spt->sp_code /= hz) == 0) spt->sp_code = 1; } done_chartimes = 1; } /* * Set up the known devices. */ y = unit * (1 << SX_CARDSHIFT); for (x = 0; x < SX_NUMCHANS; x++) { register int num; /* DTR/RTS -> RTS devices. */ num = x + y; make_dev(&sx_cdevsw, x, 0, 0, 0600, "ttyG%02d", x+y); make_dev(&sx_cdevsw, x + 0x00080, 0, 0, 0600, "cuaG%02d", num); make_dev(&sx_cdevsw, x + 0x10000, 0, 0, 0600, "ttyiG%02d", num); make_dev(&sx_cdevsw, x + 0x10080, 0, 0, 0600, "cuaiG%02d", num); make_dev(&sx_cdevsw, x + 0x20000, 0, 0, 0600, "ttylG%02d", num); make_dev(&sx_cdevsw, x + 0x20080, 0, 0, 0600, "cualG%02d", num); /* DTR/RTS -> DTR devices. */ num += SX_NUMCHANS; make_dev(&sx_cdevsw, x + 0x00008, 0, 0, 0600, "ttyG%02d", num); make_dev(&sx_cdevsw, x + 0x00088, 0, 0, 0600, "cuaG%02d", num); make_dev(&sx_cdevsw, x + 0x10008, 0, 0, 0600, "ttyiG%02d", num); make_dev(&sx_cdevsw, x + 0x10088, 0, 0, 0600, "cuaiG%02d", num); make_dev(&sx_cdevsw, x + 0x20008, 0, 0, 0600, "ttylG%02d", num); make_dev(&sx_cdevsw, x + 0x20088, 0, 0, 0600, "cualG%02d", num); } return (0); } /* * sxopen() * Open a port on behalf of a user. * * Description: * This is the standard open routine. */ static int sxopen( dev_t dev, int flag, int mode, d_thread_t *p) { int oldspl, error; int card, chan; struct sx_softc *sc; struct tty *tp; struct sx_port *pp; int mynor = minor(dev); card = SX_MINOR2CARD(mynor); if ((sc = devclass_get_softc(sx_devclass, card)) == NULL) return (ENXIO); chan = SX_MINOR2CHAN(mynor); if (chan >= SX_NUMCHANS) { DPRINT((0, DBG_OPEN|DBG_FAIL, "sx%d: nchans %d\n", card, SX_NUMCHANS)); return(ENXIO); } #ifdef POLL /* * We've now got a device, so start the poller. */ if (init_finished == 0) { timeout(sx_poll, (caddr_t)0L, sx_pollrate); init_finished = 1; } #endif /* initial/lock device */ if (DEV_IS_STATE(mynor)) { return(0); } pp = &(sc->sc_ports[chan]); tp = pp->sp_tty; /* the "real" tty */ dev->si_tty = tp; DPRINT((pp, DBG_ENTRY|DBG_OPEN, "sxopen(%s,%x,%x,%x)\n", devtoname(dev), flag, mode, p)); oldspl = spltty(); /* Keep others out */ error = 0; /* * The minor also indicates whether the DTR pin on this port is wired * as DTR or as RTS. Default is zero, wired as RTS. */ if (DEV_DTRPIN(mynor)) pp->sp_state |= SX_SS_DTRPIN; else pp->sp_state &= ~SX_SS_DTRPIN; pp->sp_state &= SX_SS_XMIT; /* Turn off "transmitting" flag. */ open_top: /* * If DTR is off and we actually do have a DTR pin, sleep waiting for * it to assert. */ while (pp->sp_state & SX_SS_DTR_OFF && SX_DTRPIN(pp)) { error = tsleep(&pp->sp_dtr_wait, TTIPRI|PCATCH, "sxdtr", 0); if (error != 0) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (DEV_IS_CALLOUT(mynor)) { if (!pp->sp_active_out) { error = EBUSY; goto out; } } else { if (pp->sp_active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&pp->sp_active_out, TTIPRI|PCATCH, "sxbi", 0); if (error != 0) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && suser(p)) { DPRINT((pp, DBG_OPEN|DBG_FAIL, "already open and EXCLUSIVE set\n")); error = EBUSY; goto out; } } else { /* * The device isn't open, so there are no conflicts. * Initialize it. Avoid sleep... :-) */ DPRINT((pp, DBG_OPEN, "first open\n")); tp->t_oproc = sx_start; tp->t_stop = sx_stop; tp->t_param = sxparam; tp->t_dev = dev; tp->t_termios = mynor & SX_CALLOUT_MASK ? pp->sp_iout : pp->sp_iin; (void)sx_modem(sc, pp, SET, TIOCM_DTR|TIOCM_RTS); ++pp->sp_wopeners; /* in case of sleep in sxparam */ error = sxparam(tp, &tp->t_termios); --pp->sp_wopeners; if (error != 0) goto out; /* XXX: we should goto_top if sxparam slept */ /* set initial DCD state */ if (DEV_IS_CALLOUT(mynor) || (sx_modem(sc, pp, GET, 0) & TIOCM_CD)) { ttyld_modem(tp, 1); } } /* whoops! we beat the close! */ if (pp->sp_state & SX_SS_CLOSING) { /* try and stop it from proceeding to bash the hardware */ pp->sp_state &= ~SX_SS_CLOSING; } /* * Wait for DCD if necessary */ if (!(tp->t_state & TS_CARR_ON) && !DEV_IS_CALLOUT(mynor) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++pp->sp_wopeners; DPRINT((pp, DBG_OPEN, "sleeping for carrier\n")); error = tsleep(TSA_CARR_ON(tp), TTIPRI|PCATCH, "sxdcd", 0); --pp->sp_wopeners; if (error != 0) goto out; goto open_top; } error = ttyld_open(tp, dev); - sx_disc_optim(tp, &tp->t_termios, pp); + pp->sp_hotchar = ttyldoptim(tp); if (tp->t_state & TS_ISOPEN && DEV_IS_CALLOUT(mynor)) pp->sp_active_out = TRUE; pp->sp_state |= SX_SS_OPEN; /* made it! */ out: splx(oldspl); DPRINT((pp, DBG_OPEN, "leaving sxopen\n")); if (!(tp->t_state & TS_ISOPEN) && pp->sp_wopeners == 0) sxhardclose(pp); return(error); } /* * sxclose() * Close a port for a user. * * Description: * This is the standard close routine. */ static int sxclose( dev_t dev, int flag, int mode, d_thread_t *p) { struct sx_port *pp; struct tty *tp; int oldspl; int error = 0; int mynor = minor(dev); if (DEV_IS_SPECIAL(mynor)) return(0); oldspl = spltty(); pp = MINOR2PP(mynor); tp = pp->sp_tty; DPRINT((pp, DBG_ENTRY|DBG_CLOSE, "sxclose(%s,%x,%x,%x) sp_state:%x\n", devtoname(dev), flag, mode, p, pp->sp_state)); /* did we sleep and lose a race? */ if (pp->sp_state & SX_SS_CLOSING) { /* error = ESOMETING? */ goto out; } /* begin race detection.. */ pp->sp_state |= SX_SS_CLOSING; sx_write_enable(pp, 0); /* block writes for ttywait() */ /* THIS MAY SLEEP IN TTYWAIT!!! */ ttyld_close(tp, flag); sx_write_enable(pp, 1); /* did we sleep and somebody started another open? */ if (!(pp->sp_state & SX_SS_CLOSING)) { /* error = ESOMETING? */ goto out; } /* ok. we are now still on the right track.. nuke the hardware */ sxhardclose(pp); ttyclose(tp); pp->sp_state &= ~SX_SS_OPEN; out: DPRINT((pp, DBG_CLOSE|DBG_EXIT, "sxclose out\n")); splx(oldspl); return(error); } /* * sxhardclose() * Do hard-close processing. * * Description: * Called on last close. Handle DTR and RTS, do cleanup. If we have * pending output in the FIFO, wait for it to clear before we shut down * the hardware. */ static void sxhardclose( struct sx_port *pp) { struct sx_softc *sc; struct tty *tp; int oldspl, dcd; oldspl = spltty(); DPRINT((pp, DBG_CLOSE, "sxhardclose sp_state:%x\n", pp->sp_state)); tp = pp->sp_tty; sc = PP2SC(pp); dcd = sx_modem(sc, pp, GET, 0) & TIOCM_CD; if (tp->t_cflag & HUPCL || (!pp->sp_active_out && !dcd && !(pp->sp_iin.c_cflag && CLOCAL)) || !(tp->t_state & TS_ISOPEN)) { disable_intr(); sx_cd1865_out(sc, CD1865_CAR, pp->sp_chan); if (sx_cd1865_in(sc, CD1865_IER|SX_EI) & CD1865_IER_TXRDY) { sx_cd1865_bic(sc, CD1865_IER, CD1865_IER_TXRDY); sx_cd1865_bis(sc, CD1865_IER, CD1865_IER_TXEMPTY); enable_intr(); splx(oldspl); ttysleep(tp, (caddr_t)pp, TTOPRI|PCATCH, "sxclose", tp->t_timeout); oldspl = spltty(); } else { enable_intr(); } (void)sx_modem(sc, pp, BIC, TIOCM_DTR|TIOCM_RTS); /* * If we should hold DTR off for a bit and we actually have a * DTR pin to hold down, schedule sxdtrwakeup(). */ if (pp->sp_dtr_wait != 0 && SX_DTRPIN(pp)) { timeout(sxdtrwakeup, pp, pp->sp_dtr_wait); pp->sp_state |= SX_SS_DTR_OFF; } } (void)sx_shutdown_chan(pp); /* Turn off the hardware. */ pp->sp_active_out = FALSE; wakeup((caddr_t)&pp->sp_active_out); wakeup(TSA_CARR_ON(tp)); splx(oldspl); } /* * called at splsoftclock()... */ static void sxdtrwakeup(void *chan) { struct sx_port *pp; int oldspl; oldspl = spltty(); pp = (struct sx_port *)chan; pp->sp_state &= ~SX_SS_DTR_OFF; wakeup(&pp->sp_dtr_wait); splx(oldspl); } /* * sxwrite() * Handle a write to a port on the I/O8+. * * Description: * This just hands processing off to the line discipline. */ static int sxwrite( dev_t dev, struct uio *uio, int flag) { struct sx_softc *sc; struct sx_port *pp; struct tty *tp; int error = 0; int mynor = minor(dev); int oldspl; pp = MINOR2PP(mynor); sc = PP2SC(pp); tp = pp->sp_tty; DPRINT((pp, DBG_WRITE, "sxwrite %s %x %x\n", devtoname(dev), uio, flag)); oldspl = spltty(); /* * If writes are currently blocked, wait on the "real" tty */ while (pp->sp_state & SX_SS_BLOCKWRITE) { pp->sp_state |= SX_SS_WAITWRITE; DPRINT((pp, DBG_WRITE, "sxwrite sleep on SX_SS_BLOCKWRITE\n")); if ((error = ttysleep(tp, (caddr_t)pp, TTOPRI|PCATCH, "sxwrite", tp->t_timeout))) { if (error == EWOULDBLOCK) error = EIO; goto out; } } error = ttyld_write(tp, uio, flag); out: splx(oldspl); DPRINT((pp, DBG_WRITE, "sxwrite out\n")); return (error); } /* * sxioctl() * Handle ioctl() processing. * * Description: * This is the standard serial ioctl() routine. It was cribbed almost * entirely from the si(4) driver. Thanks, Peter. */ static int sxioctl( dev_t dev, u_long cmd, caddr_t data, int flag, d_thread_t *p) { struct sx_softc *sc; struct sx_port *pp; struct tty *tp; int error; int mynor = minor(dev); int oldspl; int blocked = 0; #if defined(COMPAT_43) u_long oldcmd; struct termios term; #endif pp = MINOR2PP(mynor); tp = pp->sp_tty; DPRINT((pp, DBG_ENTRY|DBG_IOCTL, "sxioctl %s %lx %x %x\n", devtoname(dev), cmd, data, flag)); if (DEV_IS_STATE(mynor)) { struct termios *ct; switch (mynor & SX_STATE_MASK) { case SX_INIT_STATE_MASK: ct = DEV_IS_CALLOUT(mynor) ? &pp->sp_iout : &pp->sp_iin; break; case SX_LOCK_STATE_MASK: ct = DEV_IS_CALLOUT(mynor) ? &pp->sp_lout : &pp->sp_lin; break; default: return(ENODEV); } switch (cmd) { case TIOCSETA: error = suser(p); 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); default: return(ENOTTY); } } /* * Do the old-style ioctl compat routines... */ #if defined(COMPAT_43) 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 /* * Do the initial / lock state business */ if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { int cc; struct termios *dt = (struct termios *)data; struct termios *lt = mynor & SX_CALLOUT_MASK ? &pp->sp_lout : &pp->sp_lin; 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); 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; } /* * Block user-level writes to give the ttywait() * a chance to completely drain for commands * that require the port to be in a quiescent state. */ switch (cmd) { case TIOCSETAW: case TIOCSETAF: case TIOCDRAIN: #ifdef COMPAT_43 case TIOCSETP: #endif blocked++; /* block writes for ttywait() and sxparam() */ sx_write_enable(pp, 0); } error = ttyioctl(dev, cmd, data, flag, p); - sx_disc_optim(tp, &tp->t_termios, pp); + pp->sp_hotchar = ttyldoptim(tp); if (error != ENOTTY) goto out; oldspl = spltty(); sc = PP2SC(pp); /* Need this to do I/O to the card. */ error = 0; switch (cmd) { case TIOCSBRK: /* Send BREAK. */ DPRINT((pp, DBG_IOCTL, "sxioctl %s BRK S\n", devtoname(dev))); /* * If there's already a break state change pending or * we're already sending a break, just ignore this. * Otherwise, just set our flag and start the * transmitter. */ if (!SX_DOBRK(pp) && !SX_BREAK(pp)) { pp->sp_state |= SX_SS_DOBRK; sx_start(tp); } break; case TIOCCBRK: /* Stop sending BREAK. */ DPRINT((pp, DBG_IOCTL, "sxioctl %s BRK E\n", devtoname(dev))); /* * If a break is going, set our flag so we turn it off * when we can, then kick the transmitter. If a break * isn't going and the flag is set, turn it off. */ if (SX_BREAK(pp)) { pp->sp_state |= SX_SS_DOBRK; sx_start(tp); } else { if (SX_DOBRK(pp)) pp->sp_state &= SX_SS_DOBRK; } break; case TIOCSDTR: /* Assert DTR. */ DPRINT((pp, DBG_IOCTL, "sxioctl %s +DTR\n", devtoname(dev))); if (SX_DTRPIN(pp)) /* Using DTR? */ (void)sx_modem(sc, pp, SET, TIOCM_DTR); break; case TIOCCDTR: /* Clear DTR. */ DPRINT((pp, DBG_IOCTL, "sxioctl(%s) -DTR\n", devtoname(dev))); if (SX_DTRPIN(pp)) /* Using DTR? */ (void)sx_modem(sc, pp, SET, 0); break; case TIOCMSET: /* Force all modem signals. */ DPRINT((pp, DBG_IOCTL, "sxioctl %s =%x\n", devtoname(dev), *(int *)data)); (void)sx_modem(sc, pp, SET, *(int *)data); break; case TIOCMBIS: /* Set (some) modem signals. */ DPRINT((pp, DBG_IOCTL, "sxioctl %s +%x\n", devtoname(dev), *(int *)data)); (void)sx_modem(sc, pp, BIS, *(int *)data); break; case TIOCMBIC: /* Clear (some) modem signals. */ DPRINT((pp, DBG_IOCTL, "sxioctl %s -%x\n", devtoname(dev), *(int *)data)); (void)sx_modem(sc, pp, BIC, *(int *)data); break; case TIOCMGET: /* Get state of modem signals. */ *(int *)data = sx_modem(sc, pp, GET, 0); DPRINT((pp, DBG_IOCTL, "sxioctl(%s) got signals 0x%x\n", devtoname(dev), *(int *)data)); break; case TIOCMSDTRWAIT: /* Set "wait on close" delay. */ /* must be root since the wait applies to following logins */ error = suser(p); if (error == 0) pp->sp_dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: /* Get "wait on close" delay. */ *(int *)data = pp->sp_dtr_wait * 100 / hz; break; default: error = ENOTTY; } splx(oldspl); out: DPRINT((pp, DBG_IOCTL|DBG_EXIT, "sxioctl out %d\n", error)); if (blocked) sx_write_enable(pp, 1); return(error); } /* * sxparam() * Configure line parameters. * * Description: * Configure the bitrate, wordsize, flow control and various other serial * port parameters for this line. * * Environment: * Called at spltty(); this may sleep, does not flush nor wait for drain, * nor block writes. Caller must arrange this if it's important.. */ static int sxparam( struct tty *tp, struct termios *t) { struct sx_softc *sc; struct sx_port *pp = TP2PP(tp); int oldspl, cflag, iflag, oflag, lflag; int error = 0; int ispd = 0; int ospd = 0; unsigned char val, cor1, cor2, cor3, ier; sc = PP2SC(pp); DPRINT((pp, DBG_ENTRY|DBG_PARAM, "sxparam %x/%x\n", tp, t)); cflag = t->c_cflag; iflag = t->c_iflag; oflag = t->c_oflag; lflag = t->c_lflag; DPRINT((pp, DBG_PARAM, "OF 0x%x CF 0x%x IF 0x%x LF 0x%x\n", oflag, cflag, iflag, lflag)); /* If the port isn't hung up... */ if (t->c_ospeed != 0) { /* Convert bit rate to hardware divisor values. */ ospd = ttspeedtab(t->c_ospeed, bdrates); ispd = t->c_ispeed ? ttspeedtab(t->c_ispeed, bdrates) : ospd; /* We only allow standard bit rates. */ if (ospd < 0 || ispd < 0) return(EINVAL); } oldspl = spltty(); /* Block other activity. */ cor1 = 0; cor2 = 0; cor3 = 0; ier = CD1865_IER_RXD | CD1865_IER_CD; #ifdef notyet /* We don't yet handle this stuff. */ val = 0; if (iflag & IGNBRK) /* Breaks */ val |= BR_IGN; if (iflag & BRKINT) /* Interrupt on break? */ val |= BR_INT; if (iflag & PARMRK) /* Parity mark? */ val |= BR_PARMRK; #endif /* notyet */ /* * If the device isn't hung up, set the serial port bitrates. */ if (t->c_ospeed != 0) { disable_intr(); sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); sx_cd1865_out(sc, CD1865_RBPRH|SX_EI, (ispd >> 8) & 0xff); sx_cd1865_out(sc, CD1865_RBPRL|SX_EI, ispd & 0xff); sx_cd1865_out(sc, CD1865_TBPRH|SX_EI, (ospd >> 8) & 0xff); sx_cd1865_out(sc, CD1865_TBPRL|SX_EI, ospd & 0xff); enable_intr(); } if (cflag & CSTOPB) /* Two stop bits? */ cor1 |= CD1865_COR1_2SB; /* Yep. */ /* * Parity settings. */ val = 0; if (cflag & PARENB) { /* Parity enabled? */ val = CD1865_COR1_NORMPAR; /* Turn on normal parity handling. */ if (cflag & PARODD) /* Odd Parity? */ val |= CD1865_COR1_ODDP; /* Turn it on. */ } else val = CD1865_COR1_NOPAR; /* Turn off parity detection. */ cor1 |= val; if (iflag & IGNPAR) /* Ignore chars with parity errors? */ cor1 |= CD1865_COR1_IGNORE; /* * Set word length. */ if ((cflag & CS8) == CS8) val = CD1865_COR1_8BITS; else if ((cflag & CS7) == CS7) val = CD1865_COR1_7BITS; else if ((cflag & CS6) == CS6) val = CD1865_COR1_6BITS; else val = CD1865_COR1_5BITS; cor1 |= val; /* * Enable hardware RTS/CTS flow control. We can handle output flow * control at any time, since we have a dedicated CTS pin. * Unfortunately, though, the RTS pin is really the DTR pin. This * means that we can't ever use the automatic input flow control of * the CD1865 and that we can only use the pin for input flow * control when it's wired as RTS. */ if (cflag & CCTS_OFLOW) { /* Output flow control... */ pp->sp_state |= SX_SS_OFLOW; #ifdef SX_BROKEN_CTS disable_intr(); sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); if (sx_cd1865_in(sc, CD1865_MSVR|SX_EI) & CD1865_MSVR_CTS) { enable_intr(); pp->sp_state |= SX_SS_OSTOP; sx_write_enable(pp, 0); /* Block writes. */ } else { enable_intr(); } ier |= CD1865_IER_CTS; #else /* SX_BROKEN_CTS */ cor2 |= CD1865_COR2_CTSAE; /* Set CTS automatic enable. */ #endif /* SX_BROKEN_CTS */ } else { pp->sp_state &= ~SX_SS_OFLOW; } if (cflag & CRTS_IFLOW && !SX_DTRPIN(pp)) /* Input flow control. */ pp->sp_state |= SX_SS_IFLOW; else pp->sp_state &= ~SX_SS_IFLOW; if (iflag & IXANY) cor2 |= CD1865_COR2_IXM; /* Any character is XON. */ if (iflag & IXOFF) { cor2 |= CD1865_COR2_TXIBE; /* Enable inband flow control.*/ cor3 |= CD1865_COR3_FCT | CD1865_COR3_SCDE; /* Hide from host */ disable_intr(); sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Sel chan.*/ sx_cd1865_out(sc, CD1865_SCHR1|SX_EI, t->c_cc[VSTART]); sx_cd1865_out(sc, CD1865_SCHR2|SX_EI, t->c_cc[VSTOP]); sx_cd1865_out(sc, CD1865_SCHR3|SX_EI, t->c_cc[VSTART]); sx_cd1865_out(sc, CD1865_SCHR4|SX_EI, t->c_cc[VSTOP]); enable_intr(); } /* * All set, now program the hardware. */ disable_intr(); sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Select channel. */ sx_cd1865_out(sc, CD1865_COR1|SX_EI, cor1); sx_cd1865_out(sc, CD1865_COR2|SX_EI, cor2); sx_cd1865_out(sc, CD1865_COR3|SX_EI, cor3); sx_cd1865_wait_CCR(sc, SX_EI); sx_cd1865_out(sc, CD1865_CCR|SX_EI, CD1865_CCR_CORCHG1|CD1865_CCR_CORCHG2|CD1865_CCR_CORCHG3); sx_cd1865_wait_CCR(sc, SX_EI); enable_intr(); if (SX_DTRPIN(pp)) val = TIOCM_DTR; else val = TIOCM_RTS; if (t->c_ospeed == 0) /* Clear DTR/RTS if we're hung up. */ (void)sx_modem(sc, pp, BIC, val); else /* If we were hung up, we may have to */ (void)sx_modem(sc, pp, BIS, val); /* re-enable the signal. */ /* * Last, enable the receiver and transmitter and turn on the * interrupts we need (receive, carrier-detect and possibly CTS * (iff we're built with SX_BROKEN_CTS and CCTS_OFLOW is on). */ disable_intr(); sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Select channel. */ sx_cd1865_wait_CCR(sc, SX_EI); sx_cd1865_out(sc, CD1865_CCR|SX_EI, CD1865_CCR_RXEN|CD1865_CCR_TXEN); sx_cd1865_wait_CCR(sc, SX_EI); sx_cd1865_out(sc, CD1865_IER|SX_EI, ier); enable_intr(); DPRINT((pp, DBG_PARAM, "sxparam out\n")); splx(oldspl); return(error); } /* * sx_write_enable() * Enable/disable writes to a card channel. * * Description: * Set or clear the SX_SS_BLOCKWRITE flag in sp_state to block or allow * writes to a serial port on the card. When we enable writes, we * wake up anyone sleeping on SX_SS_WAITWRITE for this channel. * * Parameters: * flag 0 - disable writes. * 1 - enable writes. */ static void sx_write_enable( struct sx_port *pp, int flag) { int oldspl; oldspl = spltty(); /* Keep interrupts out. */ if (flag) { /* Enable writes to the channel? */ pp->sp_state &= ~SX_SS_BLOCKWRITE; /* Clear our flag. */ if (pp->sp_state & SX_SS_WAITWRITE) { /* Sleepers? */ pp->sp_state &= ~SX_SS_WAITWRITE; /* Clear their flag */ wakeup((caddr_t)pp); /* & wake them up. */ } } else /* Disabling writes. */ pp->sp_state |= SX_SS_BLOCKWRITE; /* Set our flag. */ splx(oldspl); } /* * sx_shutdown_chan() * Shut down a channel on the I/O8+. * * Description: * This does all hardware shutdown processing for a channel on the I/O8+. * It is called from sxhardclose(). We reset the channel and turn off * interrupts. */ static void sx_shutdown_chan( struct sx_port *pp) { int s; struct sx_softc *sc; DPRINT((pp, DBG_ENTRY, "sx_shutdown_chan %x %x\n", pp, pp->sp_state)); sc = PP2SC(pp); s = spltty(); disable_intr(); sx_cd1865_out(sc, CD1865_CAR, pp->sp_chan); /* Select channel. */ sx_cd1865_wait_CCR(sc, 0); /* Wait for any commands to complete. */ sx_cd1865_out(sc, CD1865_CCR, CD1865_CCR_SOFTRESET); /* Reset chan. */ sx_cd1865_wait_CCR(sc, 0); sx_cd1865_out(sc, CD1865_IER, 0); /* Disable all interrupts. */ enable_intr(); splx(s); } /* * sx_modem() * Set/Get state of modem control lines. * * Description: * Get and set the state of the modem control lines that we have available * on the I/O8+. The only lines we are guaranteed to have are CD and CTS. * We have DTR if the "DTR/RTS pin is DTR" flag is set, otherwise we have * RTS through the DTR pin. */ static int sx_modem( struct sx_softc *sc, struct sx_port *pp, enum sx_mctl cmd, int bits) { int s, x; DPRINT((pp, DBG_ENTRY|DBG_MODEM, "sx_modem %x/%s/%x\n", pp, sx_mctl2str(cmd), bits)); s = spltty(); /* Block interrupts. */ disable_intr(); sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Select our port. */ x = sx_cd1865_in(sc, CD1865_MSVR|SX_EI); /* Get the current signals. */ #ifdef SX_DEBUG DPRINT((pp, DBG_MODEM, "sx_modem MSVR 0x%x, CCSR %x GIVR %x SRSR %x\n", x, sx_cd1865_in(sc, CD1865_CCSR|SX_EI), sx_cd1865_in(sc, CD1865_GIVR|SX_EI), sx_cd1865_in(sc, CD1865_SRSR|SX_EI))); #endif enable_intr(); /* Allow other interrupts. */ switch (cmd) { case GET: bits = TIOCM_LE; if ((x & CD1865_MSVR_CD) == 0) bits |= TIOCM_CD; if ((x & CD1865_MSVR_CTS) == 0) bits |= TIOCM_CTS; if ((x & CD1865_MSVR_DTR) == 0) { if (SX_DTRPIN(pp)) /* Odd pin is DTR? */ bits |= TIOCM_DTR; /* Report DTR. */ else /* Odd pin is RTS. */ bits |= TIOCM_RTS; /* Report RTS. */ } splx(s); return(bits); case SET: x = CD1865_MSVR_OFF; if ((bits & TIOCM_RTS && !SX_DTRPIN(pp)) || (bits & TIOCM_DTR && SX_DTRPIN(pp))) x &= ~CD1865_MSVR_DTR; break; case BIS: if ((bits & TIOCM_RTS && !SX_DTRPIN(pp)) || (bits & TIOCM_DTR && SX_DTRPIN(pp))) x &= ~CD1865_MSVR_DTR; break; case BIC: if ((bits & TIOCM_RTS && !SX_DTRPIN(pp)) || (bits & TIOCM_DTR && SX_DTRPIN(pp))) x |= CD1865_MSVR_DTR; break; } DPRINT((pp, DBG_MODEM, "sx_modem MSVR=0x%x\n", x)); disable_intr(); /* * Set the new modem signals. */ sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); sx_cd1865_out(sc, CD1865_MSVR|SX_EI, x); enable_intr(); splx(s); return 0; } #ifdef POLL /* * sx_poll() * Poller to catch missed interrupts. * * Description: * Only used if we're complied with POLL. This routine is called every * sx_pollrate ticks to check for missed interrupts. We check each card * in the system; if we missed an interrupt, we complain about each one * and later call sx_intr() to handle them. */ static void sx_poll( void *dummy) { struct sx_softc *sc; struct sx_port *pp; int card, lost, oldspl, chan; DPRINT((0, DBG_POLL, "sx_poll\n")); oldspl = spltty(); if (in_interrupt) goto out; lost = 0; for (card = 0; card < sx_numunits; card++) { sc = devclass_get_softc(sx_devclass, card); if (sc == NULL) continue; if (sx_cd1865_in(sc, CD1865_SRSR|SX_EI) & CD1865_SRSR_REQint) { printf("sx%d: lost interrupt\n", card); lost++; } /* * Gripe about no input flow control. */ for (chan = 0; chan < SX_NUMCHANS; pp++, chan++) { pp = &(sc->sc_ports[chan]); if (pp->sp_delta_overflows > 0) { printf("sx%d: %d tty level buffer overflows\n", card, pp->sp_delta_overflows); pp->sp_delta_overflows = 0; } } } if (lost || sx_realpoll) sx_intr(NULL); /* call intr with fake vector */ out: splx(oldspl); timeout(sx_poll, (caddr_t)0L, sx_pollrate); } #endif /* POLL */ /* * sx_transmit() * Handle transmit request interrupt. * * Description: * This routine handles the transmit request interrupt from the CD1865 * chip on the I/O8+ card. The CD1865 interrupts us for a transmit * request under two circumstances: When the last character in the * transmit FIFO is sent and the channel is ready for more characters * ("transmit ready"), or when the last bit of the last character in the * FIFO is actually transmitted ("transmit empty"). In the former case, * we just pass processing off to sx_start() (via the line discipline) * to queue more characters. In the latter case, we were waiting for * the line to flush in sxhardclose() so we need to wake the sleeper. */ static void sx_transmit( struct sx_softc *sc, struct sx_port *pp, int card) { struct tty *tp; unsigned char flags; tp = pp->sp_tty; /* * Let others know what we're doing. */ pp->sp_state |= SX_SS_IXMIT; /* * Get the service request enable register to see what we're waiting * for. */ flags = sx_cd1865_in(sc, CD1865_SRER|SX_EI); DPRINT((pp, DBG_TRANSMIT, "sx_xmit %x SRER %x\n", tp, flags)); /* * "Transmit ready." The transmit FIFO is empty (but there are still * two characters being transmitted), so we need to tell the line * discipline to send more. */ if (flags & CD1865_IER_TXRDY) { ttyld_start(tp); pp->sp_state &= ~SX_SS_IXMIT; DPRINT((pp, DBG_TRANSMIT, "sx_xmit TXRDY out\n")); return; } /* * "Transmit empty." The transmitter is completely empty; turn off the * service request and wake up the guy in sxhardclose() who is waiting * for this. */ if (flags & CD1865_IER_TXEMPTY) { flags &= ~CD1865_IER_TXEMPTY; sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); sx_cd1865_out(sc, CD1865_SRER|SX_EI, flags); wakeup((caddr_t)pp); } pp->sp_state &= ~SX_SS_IXMIT; DPRINT((pp, DBG_TRANSMIT, "sx_xmit out\n")); } /* * sx_modem_state() * Handle modem state-change request interrupt. * * Description: * Handles changed modem signals CD and CTS. We pass the CD change * off to the line discipline. We can't handle DSR since there isn't a * pin for it. */ static void sx_modem_state( struct sx_softc *sc, struct sx_port *pp, int card) { struct tty *tp; unsigned char mcr; /* * Let others know what we're doing. */ pp->sp_state |= SX_SS_IMODEM; tp = pp->sp_tty; /* Grab the Modem Change Register. */ mcr = sx_cd1865_in(sc, CD1865_MCR|SX_EI); DPRINT((pp, DBG_MODEM_STATE, "sx_mdmst %x st %x sp %x mcr %x\n", tp, tp->t_state, pp->sp_state, mcr)); if (mcr & CD1865_MCR_CDCHG) { /* CD changed? */ if ((sx_cd1865_in(sc, CD1865_MSVR) & CD1865_MSVR_CD) == 0) { DPRINT((pp, DBG_INTR, "modem carr on t_line %d\n", tp->t_line)); (void)ttyld_modem(tp, 1); } else { /* CD went down. */ DPRINT((pp, DBG_INTR, "modem carr off\n")); if (ttyld_modem(tp, 0)) (void)sx_modem(sc, pp, SET, 0); } } #ifdef SX_BROKEN_CTS if (mcr & CD1865_MCR_CTSCHG) { /* CTS changed? */ if (sx_cd1865_in(sc, CD1865_MSVR|SX_EI) & CD1865_MSVR_CTS) { pp->sp_state |= SX_SS_OSTOP; sx_cd1865_bic(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY); sx_write_enable(pp, 0); /* Block writes. */ } else { pp->sp_state &= ~SX_SS_OSTOP; sx_cd1865_bis(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY); sx_write_enable(pp, 1); /* Unblock writes. */ } } #endif /* SX_BROKEN_CTS */ /* Clear state-change indicator bits. */ sx_cd1865_out(sc, CD1865_MCR|SX_EI, 0); pp->sp_state &= ~SX_SS_IMODEM; } /* * sx_receive() * Handle receive request interrupt. * * Description: * Handle a receive request interrupt from the CD1865. This is just a * standard "we have characters to process" request, we don't have to * worry about exceptions like BREAK and such. Exceptions are handled * by sx_receive_exception(). */ static void sx_receive( struct sx_softc *sc, struct sx_port *pp, int card) { struct tty *tp; unsigned char count; int i, x; static unsigned char sx_rxbuf[SX_BUFFERSIZE]; /* input staging area */ tp = pp->sp_tty; DPRINT((pp, DBG_RECEIVE, "sx_rcv %x st %x sp %x\n", tp, tp->t_state, pp->sp_state)); /* * Let others know what we're doing. */ pp->sp_state |= SX_SS_IRCV; /* * How many characters are waiting for us? */ count = sx_cd1865_in(sc, CD1865_RDCR|SX_EI); if (count == 0) /* None? Bail. */ return; DPRINT((pp, DBG_RECEIVE, "sx_receive count %d\n", count)); /* * Pull the characters off the card into our local buffer, then * process that. */ for (i = 0; i < count; i++) sx_rxbuf[i] = sx_cd1865_in(sc, CD1865_RDR|SX_EI); /* * If we're not open and connected, bail. */ if (!(tp->t_state & TS_CONNECTED && tp->t_state & TS_ISOPEN)) { pp->sp_state &= ~SX_SS_IRCV; DPRINT((pp, DBG_RECEIVE, "sx_rcv not open\n")); return; } /* * If the tty input buffers are blocked and we have an RTS pin, * drop RTS and bail. */ if (tp->t_state & TS_TBLOCK) { if (!SX_DTRPIN(pp) && SX_IFLOW(pp)) { (void)sx_modem(sc, pp, BIC, TIOCM_RTS); pp->sp_state |= SX_SS_ISTOP; } pp->sp_state &= ~SX_SS_IRCV; return; } if (tp->t_state & TS_CAN_BYPASS_L_RINT) { DPRINT((pp, DBG_RECEIVE, "sx_rcv BYPASS\n")); /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or * echo!). slinput is reasonably fast (usually 40 * instructions plus call overhead). */ if (tp->t_rawq.c_cc + count >= SX_I_HIGH_WATER && (tp->t_cflag & CRTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) { ttyblock(tp); DPRINT((pp, DBG_RECEIVE, "sx_rcv block\n")); } tk_nin += count; tk_rawcc += count; tp->t_rawcc += count; pp->sp_delta_overflows += b_to_q((char *)sx_rxbuf, count, &tp->t_rawq); ttwakeup(tp); /* * If we were stopped and need to start again because of this * receive, kick the output routine to get things going again. */ if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; sx_start(tp); } } else { DPRINT((pp, DBG_RECEIVE, "sx_rcv l_rint\n")); /* * It'd be nice to not have to go through the function call * overhead for each char here. It'd be nice to block input * it, saving a loop here and the call/return overhead. */ for (x = 0; x < count; x++) { i = sx_rxbuf[x]; if (ttyld_rint(tp, i) == -1) pp->sp_delta_overflows++; } } pp->sp_state &= ~SX_SS_IRCV; DPRINT((pp, DBG_RECEIVE, "sx_rcv out\n")); } /* * sx_receive_exception() * Handle receive exception request interrupt processing. * * Description: * Handle a receive exception request interrupt from the CD1865. * Possible exceptions include BREAK, overrun, receiver timeout * and parity and frame errors. We don't handle receiver timeout, * we just complain. The rest are passed to ttyinput(). */ static void sx_receive_exception( struct sx_softc *sc, struct sx_port *pp, int card) { struct tty *tp; unsigned char st; int ch, isopen; tp = pp->sp_tty; /* * Let others know what we're doing. */ pp->sp_state |= SX_SS_IRCVEXC; /* * Check to see whether we should receive characters. */ if (tp->t_state & TS_CONNECTED && tp->t_state & TS_ISOPEN) isopen = 1; else isopen = 0; st = sx_cd1865_in(sc, CD1865_RCSR|SX_EI); /* Get the character status.*/ ch = (int)sx_cd1865_in(sc, CD1865_RDR|SX_EI); /* Get the character. */ DPRINT((pp, DBG_RECEIVE_EXC, "sx_rexc %x st %x sp %x st 0x%x ch 0x%x ('%c')\n", tp, tp->t_state, pp->sp_state, st, ch, ch)); /* If there's no status or the tty isn't open, bail. */ if (!st || !isopen) { pp->sp_state &= ~SX_SS_IRCVEXC; DPRINT((pp, DBG_RECEIVE_EXC, "sx_rexc not open\n")); return; } if (st & CD1865_RCSR_TOUT) /* Receiver timeout; just complain. */ printf("sx%d: port %d: Receiver timeout.\n", card, pp->sp_chan); else if (st & CD1865_RCSR_BREAK) ch |= TTY_BI; else if (st & CD1865_RCSR_PE) ch |= TTY_PE; else if (st & CD1865_RCSR_FE) ch |= TTY_FE; else if (st & CD1865_RCSR_OE) ch |= TTY_OE; ttyld_rint(tp, ch); pp->sp_state &= ~SX_SS_IRCVEXC; } /* * sx_intr() * Field interrupts from the I/O8+. * * Description: * The interrupt handler polls ALL ports on ALL adapters each time * it is called. */ void sx_intr( void *arg) { struct sx_softc *sc; struct sx_port *pp = NULL; int card; unsigned char ack; sc = arg; DPRINT((0, arg == NULL ? DBG_POLL:DBG_INTR, "sx_intr\n")); if (in_interrupt) return; in_interrupt = 1; /* * When we get an int we poll all the channels and do ALL pending * work, not just the first one we find. This allows all cards to * share the same vector. * * On the other hand, if we're sharing the vector with something * that's not an I/O8+, we may be making extra work for ourselves. */ for (card = 0; card < sx_numunits; card++) { unsigned char st; sc = devclass_get_softc(sx_devclass, card); if (sc == NULL) continue; /* * Check the Service Request Status Register to see who * interrupted us and why. May be a receive, transmit or * modem-signal-change interrupt. Reading the appropriate * Request Acknowledge Register acknowledges the request and * gives us the contents of the Global Service Vector Register, * which in a daisy-chained configuration (not ours) uniquely * identifies the particular CD1865 and gives us the request * type. We mask off the ID part and use the rest. * * From the CD1865 specs, it appears that only one request can * happen at a time, but in testing it's pretty obvious that * the specs lie. Or perhaps we're just slow enough that the * requests pile up. Regardless, if we try to process more * than one at a time without clearing the previous request * (writing zero to EOIR) first, we hang the card. Thus the * "else if" logic here. */ while ((st = (sx_cd1865_in(sc, CD1865_SRSR|SX_EI)) & CD1865_SRSR_REQint)) { /* * Transmit request interrupt. */ if (st & CD1865_SRSR_TREQint) { ack = sx_cd1865_in(sc, CD1865_TRAR|SX_EI) & CD1865_GIVR_ITMASK; pp = sx_int_port(sc, card); if (pp == NULL) /* Bad channel. */ goto skip; pp->sp_state |= SX_SS_INTR; /* In interrupt. */ if (ack == CD1865_GIVR_IT_TX) sx_transmit(sc, pp, card); else printf("sx%d: Bad transmit ack 0x%02x.\n", card, ack); } /* * Modem signal change request interrupt. */ else if (st & CD1865_SRSR_MREQint) { ack = sx_cd1865_in(sc, CD1865_MRAR|SX_EI) & CD1865_GIVR_ITMASK; pp = sx_int_port(sc, card); if (pp == NULL) /* Bad channel. */ goto skip; pp->sp_state |= SX_SS_INTR; /* In interrupt. */ if (ack == CD1865_GIVR_IT_MODEM) sx_modem_state(sc, pp, card); else printf("sx%d: Bad modem ack 0x%02x.\n", card, ack); } /* * Receive request interrupt. */ else if (st & CD1865_SRSR_RREQint) { ack = sx_cd1865_in(sc, CD1865_RRAR|SX_EI) & CD1865_GIVR_ITMASK; pp = sx_int_port(sc, card); if (pp == NULL) /* Bad channel. */ goto skip; pp->sp_state |= SX_SS_INTR; /* In interrupt. */ if (ack == CD1865_GIVR_IT_RCV) sx_receive(sc, pp, card); else if (ack == CD1865_GIVR_IT_REXC) sx_receive_exception(sc, pp, card); else printf("sx%d: Bad receive ack 0x%02x.\n", card, ack); } /* * None of the above; this is a "can't happen," but * you never know... */ else { printf("sx%d: Bad service request 0x%02x.\n", card, st); } pp->sp_state &= ~SX_SS_INTR; skip: sx_cd1865_out(sc, CD1865_EOIR|SX_EI, 0); /* EOI. */ } /* while (st & CD1865_SRSR_REQint) */ } /* for (card = 0; card < sx_numunits; card++) */ in_interrupt = 0; DPRINT((0, arg == NULL ? DBG_POLL:DBG_INTR, "sx_intr out\n")); } /* * sx_start() * Handle transmit and state-change stuff. * * Description: * This is part of the line discipline processing; at various points in * the line discipline he calls ttstart() which calls the oproc routine, * which is this function. We're called by the line discipline to start * data transmission and to change signal states (for RTS flow control). * We're also called by this driver to perform line-breaks and to actually * do the data transmission. * We can only fill the FIFO from interrupt since the card only makes it * available to us during a service request such as TXRDY; this only * happens at interrupt. * * All paths through this code call ttwwakeup(). */ static void sx_start( struct tty *tp) { struct sx_softc *sc; struct sx_port *pp; struct clist *qp; int s; int count = CD1865_TFIFOSZ; s = spltty(); pp = TP2PP(tp); qp = &tp->t_outq; DPRINT((pp, DBG_ENTRY|DBG_START, "sx_start %x st %x sp %x cc %d\n", tp, tp->t_state, pp->sp_state, qp->c_cc)); /* * If we're stopped, just wake up sleepers and get out. */ if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP)) { ttwwakeup(tp); splx(s); DPRINT((pp, DBG_EXIT|DBG_START, "sx_start out\n", tp->t_state)); return; } sc = TP2SC(tp); /* * If we're not transmitting, we may have been called to crank up the * transmitter and start things rolling or we may have been called to * get a bit of tty state. If the latter, handle it. Either way, if * we have data to transmit, turn on the transmit-ready interrupt, * set the XMIT flag and we're done. As soon as we allow interrupts * the card will interrupt for the first chunk of data. Note that * we don't mark the tty as busy until we are actually sending data * and then only if we have more than will fill the FIFO. If there's * no data to transmit, just handle the tty state. */ if (!SX_XMITTING(pp)) { /* * If we were flow-controlled and input is no longer blocked, * raise RTS if we can. */ if (SX_ISTOP(pp) && !(tp->t_state & TS_TBLOCK)) { if (!SX_DTRPIN(pp) && SX_IFLOW(pp)) (void)sx_modem(sc, pp, BIS, TIOCM_RTS); pp->sp_state &= ~SX_SS_ISTOP; } /* * If input is blocked, drop RTS if we can and set our flag. */ if (tp->t_state & TS_TBLOCK) { if (!SX_DTRPIN(pp) && SX_IFLOW(pp)) (void)sx_modem(sc, pp, BIC, TIOCM_RTS); pp->sp_state |= SX_SS_ISTOP; } if ((qp->c_cc > 0 && !SX_OSTOP(pp)) || SX_DOBRK(pp)) { disable_intr(); sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); sx_cd1865_bis(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY); enable_intr(); pp->sp_state |= SX_SS_XMIT; } ttwwakeup(tp); splx(s); DPRINT((pp, DBG_EXIT|DBG_START, "sx_start out B st %x sp %x cc %d\n", tp->t_state, pp->sp_state, qp->c_cc)); return; } /* * If we weren't called from an interrupt or it wasn't a transmit * interrupt, we've done all we need to do. Everything else is done * in the transmit interrupt. */ if (!SX_INTR(pp) || !SX_IXMIT(pp)) { ttwwakeup(tp); splx(s); DPRINT((pp, DBG_EXIT|DBG_START, "sx_start out X\n")); return; } /* * We're transmitting. If the clist is empty and we don't have a break * to send, turn off transmit-ready interrupts, and clear the XMIT * flag. Mark the tty as no longer busy, in case we haven't done * that yet. A future call to sxwrite() with more characters will * start up the process once more. */ if (qp->c_cc == 0 && !SX_DOBRK(pp)) { disable_intr(); /* sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan);*/ sx_cd1865_bic(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY); enable_intr(); pp->sp_state &= ~SX_SS_XMIT; tp->t_state &= ~TS_BUSY; ttwwakeup(tp); splx(s); DPRINT((pp, DBG_EXIT|DBG_START, "sx_start out E st %x sp %x\n", tp->t_state, pp->sp_state)); return; } disable_intr(); /* * If we have a BREAK state-change pending, handle it. If we aren't * sending a break, start one. If we are, turn it off. */ if (SX_DOBRK(pp)) { count -= 2; /* Account for escape chars in FIFO. */ if (SX_BREAK(pp)) { /* Doing break, stop it. */ sx_cd1865_out(sc, CD1865_TDR, CD1865_C_ESC); sx_cd1865_out(sc, CD1865_TDR, CD1865_C_EBRK); sx_cd1865_etcmode(sc, SX_EI, pp->sp_chan, 0); pp->sp_state &= ~SX_SS_BREAK; } else { /* Start doing break. */ sx_cd1865_etcmode(sc, SX_EI, pp->sp_chan, 1); sx_cd1865_out(sc, CD1865_TDR, CD1865_C_ESC); sx_cd1865_out(sc, CD1865_TDR, CD1865_C_SBRK); pp->sp_state |= SX_SS_BREAK; } pp->sp_state &= ~SX_SS_DOBRK; } /* * We've still got data in the clist, fill the channel's FIFO. The * CD1865 only gives us access to the FIFO during a transmit ready * request [interrupt] for this channel. */ while (qp->c_cc > 0 && count-- >= 0) { register unsigned char ch, *cp; int nch; ch = (char)getc(qp); /* * If we're doing a break we're in ETC mode, so we need to * double any NULs in the stream. */ if (SX_BREAK(pp)) { /* Doing break, in ETC mode. */ if (ch == '\0') { /* NUL? Double it. */ sx_cd1865_out(sc, CD1865_TDR, ch); count--; } /* * Peek the next character; if it's a NUL, we need * to escape it, but we can't if we're out of FIFO. * We'll do it on the next pass and leave the FIFO * incompletely filled. */ if (qp->c_cc > 0) { cp = qp->c_cf; cp = nextc(qp, cp, &nch); if (nch == '\0' && count < 1) count = -1; } } sx_cd1865_out(sc, CD1865_TDR, ch); } enable_intr(); /* * If we still have data to transmit, mark the tty busy for the * line discipline. */ if (qp->c_cc > 0) tp->t_state |= TS_BUSY; else tp->t_state &= ~TS_BUSY; /* Wake up sleepers if necessary. */ ttwwakeup(tp); splx(s); DPRINT((pp, DBG_EXIT|DBG_START, "sx_start out R %d/%d\n", count, qp->c_cc)); } /* * Stop output on a line. called at spltty(); */ void sx_stop( struct tty *tp, int rw) { struct sx_softc *sc; struct sx_port *pp; int s; sc = TP2SC(tp); pp = TP2PP(tp); DPRINT((TP2PP(tp), DBG_ENTRY|DBG_STOP, "sx_stop(%x,%x)\n", tp, rw)); s = spltty(); /* XXX: must check (rw & FWRITE | FREAD) etc flushing... */ if (rw & FWRITE) { disable_intr(); sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); sx_cd1865_bic(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY); sx_cd1865_wait_CCR(sc, SX_EI); /* Wait for CCR to go idle. */ sx_cd1865_out(sc, CD1865_CCR|SX_EI, CD1865_CCR_TXDIS); sx_cd1865_wait_CCR(sc, SX_EI); enable_intr(); /* what level are we meant to be flushing anyway? */ if (tp->t_state & TS_BUSY) { if ((tp->t_state & TS_TTSTOP) == 0) tp->t_state |= TS_FLUSH; tp->t_state &= ~TS_BUSY; ttwwakeup(tp); } } /* * Nothing to do for FREAD. */ splx(s); } -static void -sx_disc_optim( - struct tty *tp, - struct termios *t, - struct sx_port *pp) -{ - /* - * If we're in "raw" mode, we can bypass ttyinput(). - */ - if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && - (!(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; - pp->sp_hotchar = linesw[tp->t_line].l_hotchar; - DPRINT((pp, DBG_OPTIM, "sx_disc_optim: bypass %s, hotchar %x\n", - (tp->t_state & TS_CAN_BYPASS_L_RINT) ? "yes" : "no", - pp->sp_hotchar)); -} - - #ifdef SX_DEBUG void sx_dprintf( struct sx_port *pp, int flags, const char *fmt, ...) { static char *logbuf = NULL; static char *linebuf = NULL; static char *logptr; char *lbuf; int n, m; va_list ap; if (logbuf == NULL) { logbuf = (char *)malloc(1024*1024, M_DEVBUF, M_WAITOK); linebuf = (char *)malloc(256, M_DEVBUF, M_WAITOK); logptr = logbuf; } lbuf = linebuf; n = 0; if ((pp == NULL && (sx_debug&flags)) || (pp != NULL && ((pp->sp_debug&flags) || (sx_debug&flags)))) { if (pp != NULL && pp->sp_tty != NULL && pp->sp_tty->t_dev != NULL) { n = snprintf(linebuf, 256, "%cx%d(%d): ", 's', (int)SX_MINOR2CARD(minor(pp->sp_tty->t_dev)), (int)SX_MINOR2CHAN(minor(pp->sp_tty->t_dev))); if (n > 256) n = 256; lbuf += n; } m = n; va_start(ap, fmt); n = vsnprintf(lbuf, 256 - m, fmt, ap); va_end(ap); if (n > 256 - m) n = 256 - m; n += m; if (logptr + n + 1 > logbuf + (1024 * 1024)) { bzero(logptr, logbuf + (1024 * 1024) - logptr); logptr = logbuf; } bcopy(linebuf, logptr, n); logptr += n; *logptr = '\0'; if (sx_debug & DBG_PRINTF) printf("%s", linebuf); } } static char * sx_mctl2str(enum sx_mctl cmd) { switch (cmd) { case GET: return("GET"); case SET: return("SET"); case BIS: return("BIS"); case BIC: return("BIC"); } return("BAD"); } #endif /* DEBUG */ diff --git a/sys/dev/usb/ucom.c b/sys/dev/usb/ucom.c index 95ac0ab345ee..b28a0f08dce3 100644 --- a/sys/dev/usb/ucom.c +++ b/sys/dev/usb/ucom.c @@ -1,1188 +1,1169 @@ /* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */ /*- * Copyright (c) 2001-2002, Shunsuke Akiyama . * 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. */ #include __FBSDID("$FreeBSD$"); /* * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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 NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * TODO: * 1. How do I handle hotchar? */ #include #include #include #include #include #include #include #include #include #include #include #include #if __FreeBSD_version >= 500014 #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef USB_DEBUG static int ucomdebug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom"); SYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RW, &ucomdebug, 0, "ucom debug level"); #define DPRINTF(x) do { \ if (ucomdebug) \ logprintf x; \ } while (0) #define DPRINTFN(n, x) do { \ if (ucomdebug > (n)) \ logprintf x; \ } while (0) #else #define DPRINTF(x) #define DPRINTFN(n, x) #endif Static d_open_t ucomopen; Static d_close_t ucomclose; Static d_read_t ucomread; Static d_write_t ucomwrite; Static d_ioctl_t ucomioctl; static struct cdevsw ucom_cdevsw = { .d_version = D_VERSION, .d_open = ucomopen, .d_close = ucomclose, .d_read = ucomread, .d_write = ucomwrite, .d_ioctl = ucomioctl, .d_name = "ucom", .d_flags = D_TTY | D_NEEDGIANT, #if __FreeBSD_version < 500014 .d_bmaj = -1, #endif }; Static void ucom_cleanup(struct ucom_softc *); Static int ucomctl(struct ucom_softc *, int, int); Static int ucomparam(struct tty *, struct termios *); Static void ucomstart(struct tty *); Static void ucomstop(struct tty *, int); Static void ucom_shutdown(struct ucom_softc *); Static void ucom_dtr(struct ucom_softc *, int); Static void ucom_rts(struct ucom_softc *, int); Static void ucom_break(struct ucom_softc *, int); Static usbd_status ucomstartread(struct ucom_softc *); Static void ucomreadcb(usbd_xfer_handle, usbd_private_handle, usbd_status); Static void ucomwritecb(usbd_xfer_handle, usbd_private_handle, usbd_status); Static void ucomstopread(struct ucom_softc *); -static void disc_optim(struct tty *, struct termios *, struct ucom_softc *); devclass_t ucom_devclass; static moduledata_t ucom_mod = { "ucom", NULL, NULL }; DECLARE_MODULE(ucom, ucom_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_DEPEND(ucom, usb, 1, 1, 1); MODULE_VERSION(ucom, UCOM_MODVER); int ucom_attach(struct ucom_softc *sc) { struct tty *tp; int unit; unit = device_get_unit(sc->sc_dev); sc->sc_tty = tp = ttymalloc(sc->sc_tty); tp->t_oproc = ucomstart; tp->t_param = ucomparam; tp->t_stop = ucomstop; DPRINTF(("ucom_attach: tty_attach tp = %p\n", tp)); DPRINTF(("ucom_attach: make_dev: ucom%d\n", unit)); sc->dev = make_dev(&ucom_cdevsw, unit | UCOM_CALLOUT_MASK, UID_UUCP, GID_DIALER, 0660, "ucom%d", unit); sc->dev->si_tty = tp; return (0); } int ucom_detach(struct ucom_softc *sc) { struct tty *tp = sc->sc_tty; int s; DPRINTF(("ucom_detach: sc = %p, tp = %p\n", sc, sc->sc_tty)); sc->sc_dying = 1; if (sc->sc_bulkin_pipe != NULL) usbd_abort_pipe(sc->sc_bulkin_pipe); if (sc->sc_bulkout_pipe != NULL) usbd_abort_pipe(sc->sc_bulkout_pipe); if (tp != NULL) { if (tp->t_state & TS_ISOPEN) { device_printf(sc->sc_dev, "still open, forcing close\n"); ttyld_close(tp, 0); ttyclose(tp); } } else { DPRINTF(("ucom_detach: no tty\n")); return (0); } s = splusb(); if (--sc->sc_refcnt >= 0) { /* Wait for processes to go away. */ usb_detach_wait(USBDEV(sc->sc_dev)); } splx(s); destroy_dev(sc->dev); return (0); } Static void ucom_shutdown(struct ucom_softc *sc) { struct tty *tp = sc->sc_tty; DPRINTF(("ucom_shutdown\n")); /* * Hang up if necessary. Wait a bit, so the other side has time to * notice even if we immediately open the port again. */ if (ISSET(tp->t_cflag, HUPCL)) { (void)ucomctl(sc, TIOCM_DTR, DMBIC); (void)tsleep(sc, TTIPRI, "ucomsd", hz); } } Static int ucomopen(dev_t dev, int flag, int mode, usb_proc_ptr p) { int unit = UCOMUNIT(dev); struct ucom_softc *sc; usbd_status err; struct tty *tp; int s; int error; USB_GET_SC_OPEN(ucom, unit, sc); if (sc->sc_dying) return (ENXIO); tp = sc->sc_tty; DPRINTF(("%s: ucomopen: tp = %p\n", USBDEVNAME(sc->sc_dev), tp)); if (ISSET(tp->t_state, TS_ISOPEN) && ISSET(tp->t_state, TS_XCLUDE) && suser(p)) return (EBUSY); /* * Do the following iff this is a first open. */ s = spltty(); while (sc->sc_opening) tsleep(&sc->sc_opening, PRIBIO, "ucomop", 0); sc->sc_opening = 1; if (!ISSET(tp->t_state, TS_ISOPEN)) { struct termios t; sc->sc_poll = 0; sc->sc_lsr = sc->sc_msr = sc->sc_mcr = 0; tp->t_dev = dev; /* * Initialize the termios status to the defaults. Add in the * sticky bits from TIOCSFLAGS. */ t.c_ispeed = 0; t.c_ospeed = TTYDEF_SPEED; t.c_cflag = TTYDEF_CFLAG; /* Make sure ucomparam() will do something. */ tp->t_ospeed = 0; (void)ucomparam(tp, &t); tp->t_iflag = TTYDEF_IFLAG; tp->t_oflag = TTYDEF_OFLAG; tp->t_lflag = TTYDEF_LFLAG; ttychars(tp); ttsetwater(tp); /* * Turn on DTR. We must always do this, even if carrier is not * present, because otherwise we'd have to use TIOCSDTR * immediately after setting CLOCAL, which applications do not * expect. We always assert DTR while the device is open * unless explicitly requested to deassert it. */ (void)ucomctl(sc, TIOCM_DTR | TIOCM_RTS, DMBIS); /* Device specific open */ if (sc->sc_callback->ucom_open != NULL) { error = sc->sc_callback->ucom_open(sc->sc_parent, sc->sc_portno); if (error) { ucom_cleanup(sc); sc->sc_opening = 0; wakeup(&sc->sc_opening); splx(s); return (error); } } DPRINTF(("ucomopen: open pipes in = %d out = %d\n", sc->sc_bulkin_no, sc->sc_bulkout_no)); /* Open the bulk pipes */ /* Bulk-in pipe */ err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkin_no, 0, &sc->sc_bulkin_pipe); if (err) { printf("%s: open bulk in error (addr %d): %s\n", USBDEVNAME(sc->sc_dev), sc->sc_bulkin_no, usbd_errstr(err)); error = EIO; goto fail_0; } /* Bulk-out pipe */ err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkout_no, USBD_EXCLUSIVE_USE, &sc->sc_bulkout_pipe); if (err) { printf("%s: open bulk out error (addr %d): %s\n", USBDEVNAME(sc->sc_dev), sc->sc_bulkout_no, usbd_errstr(err)); error = EIO; goto fail_1; } /* Allocate a request and an input buffer and start reading. */ sc->sc_ixfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_ixfer == NULL) { error = ENOMEM; goto fail_2; } sc->sc_ibuf = usbd_alloc_buffer(sc->sc_ixfer, sc->sc_ibufsizepad); if (sc->sc_ibuf == NULL) { error = ENOMEM; goto fail_3; } sc->sc_oxfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_oxfer == NULL) { error = ENOMEM; goto fail_3; } sc->sc_obuf = usbd_alloc_buffer(sc->sc_oxfer, sc->sc_obufsize + sc->sc_opkthdrlen); if (sc->sc_obuf == NULL) { error = ENOMEM; goto fail_4; } /* * Handle initial DCD. */ if (ISSET(sc->sc_msr, UMSR_DCD) || (minor(dev) & UCOM_CALLOUT_MASK)) ttyld_modem(tp, 1); ucomstartread(sc); } sc->sc_opening = 0; wakeup(&sc->sc_opening); splx(s); error = ttyopen(dev, tp); if (error) goto bad; error = ttyld_open(tp, dev); if (error) goto bad; - disc_optim(tp, &tp->t_termios, sc); + sc->hotchar = ttyldoptim(tp); DPRINTF(("%s: ucomopen: success\n", USBDEVNAME(sc->sc_dev))); sc->sc_poll = 1; sc->sc_refcnt++; return (0); fail_4: usbd_free_xfer(sc->sc_oxfer); sc->sc_oxfer = NULL; fail_3: usbd_free_xfer(sc->sc_ixfer); sc->sc_ixfer = NULL; fail_2: usbd_close_pipe(sc->sc_bulkout_pipe); sc->sc_bulkout_pipe = NULL; fail_1: usbd_close_pipe(sc->sc_bulkin_pipe); sc->sc_bulkin_pipe = NULL; fail_0: sc->sc_opening = 0; wakeup(&sc->sc_opening); splx(s); return (error); bad: if (!ISSET(tp->t_state, TS_ISOPEN)) { /* * We failed to open the device, and nobody else had it opened. * Clean up the state as appropriate. */ ucom_cleanup(sc); } DPRINTF(("%s: ucomopen: failed\n", USBDEVNAME(sc->sc_dev))); return (error); } static int ucomclose(dev_t dev, int flag, int mode, usb_proc_ptr p) { struct ucom_softc *sc; struct tty *tp; int s; USB_GET_SC(ucom, UCOMUNIT(dev), sc); tp = sc->sc_tty; DPRINTF(("%s: ucomclose: unit = %d\n", USBDEVNAME(sc->sc_dev), UCOMUNIT(dev))); if (!ISSET(tp->t_state, TS_ISOPEN)) goto quit; s = spltty(); ttyld_close(tp, flag); - disc_optim(tp, &tp->t_termios, sc); + sc->hotchar = ttyldoptim(tp); ttyclose(tp); splx(s); if (sc->sc_dying) goto quit; if (!ISSET(tp->t_state, TS_ISOPEN)) { /* * Although we got a last close, the device may still be in * use; e.g. if this was the dialout node, and there are still * processes waiting for carrier on the non-dialout node. */ ucom_cleanup(sc); } if (sc->sc_callback->ucom_close != NULL) sc->sc_callback->ucom_close(sc->sc_parent, sc->sc_portno); quit: if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (0); } static int ucomread(dev_t dev, struct uio *uio, int flag) { struct ucom_softc *sc; struct tty *tp; int error; USB_GET_SC(ucom, UCOMUNIT(dev), sc); tp = sc->sc_tty; DPRINTF(("ucomread: tp = %p, flag = 0x%x\n", tp, flag)); if (sc->sc_dying) return (EIO); error = ttyld_read(tp, uio, flag); DPRINTF(("ucomread: error = %d\n", error)); return (error); } static int ucomwrite(dev_t dev, struct uio *uio, int flag) { struct ucom_softc *sc; struct tty *tp; int error; USB_GET_SC(ucom, UCOMUNIT(dev), sc); tp = sc->sc_tty; DPRINTF(("ucomwrite: tp = %p, flag = 0x%x\n", tp, flag)); if (sc->sc_dying) return (EIO); error = ttyld_write(tp, uio, flag); DPRINTF(("ucomwrite: error = %d\n", error)); return (error); } static int ucomioctl(dev_t dev, u_long cmd, caddr_t data, int flag, usb_proc_ptr p) { struct ucom_softc *sc; struct tty *tp; int error; int s; int d; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) u_long oldcmd; struct termios term; #endif USB_GET_SC(ucom, UCOMUNIT(dev), sc); tp = sc->sc_tty; if (sc->sc_dying) return (EIO); DPRINTF(("ucomioctl: cmd = 0x%08lx\n", cmd)); #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 error = ttyioctl(dev, cmd, data, flag, p); - disc_optim(tp, &tp->t_termios, sc); + sc->hotchar = ttyldoptim(tp); if (error != ENOTTY) { DPRINTF(("ucomioctl: l_ioctl: error = %d\n", error)); return (error); } s = spltty(); if (sc->sc_callback->ucom_ioctl != NULL) { error = sc->sc_callback->ucom_ioctl(sc->sc_parent, sc->sc_portno, cmd, data, flag, p); if (error >= 0) return (error); } error = 0; DPRINTF(("ucomioctl: our cmd = 0x%08lx\n", cmd)); switch (cmd) { case TIOCSBRK: DPRINTF(("ucomioctl: TIOCSBRK\n")); ucom_break(sc, 1); break; case TIOCCBRK: DPRINTF(("ucomioctl: TIOCCBRK\n")); ucom_break(sc, 0); break; case TIOCSDTR: DPRINTF(("ucomioctl: TIOCSDTR\n")); (void)ucomctl(sc, TIOCM_DTR, DMBIS); break; case TIOCCDTR: DPRINTF(("ucomioctl: TIOCCDTR\n")); (void)ucomctl(sc, TIOCM_DTR, DMBIC); break; case TIOCMSET: d = *(int *)data; DPRINTF(("ucomioctl: TIOCMSET, 0x%x\n", d)); (void)ucomctl(sc, d, DMSET); break; case TIOCMBIS: d = *(int *)data; DPRINTF(("ucomioctl: TIOCMBIS, 0x%x\n", d)); (void)ucomctl(sc, d, DMBIS); break; case TIOCMBIC: d = *(int *)data; DPRINTF(("ucomioctl: TIOCMBIC, 0x%x\n", d)); (void)ucomctl(sc, d, DMBIC); break; case TIOCMGET: d = ucomctl(sc, 0, DMGET); DPRINTF(("ucomioctl: TIOCMGET, 0x%x\n", d)); *(int *)data = d; break; default: DPRINTF(("ucomioctl: error: our cmd = 0x%08lx\n", cmd)); error = ENOTTY; break; } splx(s); return (error); } Static int ucomctl(struct ucom_softc *sc, int bits, int how) { int mcr; int msr; int onoff; DPRINTF(("ucomctl: bits = 0x%x, how = %d\n", bits, how)); if (how == DMGET) { SET(bits, TIOCM_LE); /* always set TIOCM_LE bit */ DPRINTF(("ucomctl: DMGET: LE")); mcr = sc->sc_mcr; if (ISSET(mcr, UMCR_DTR)) { SET(bits, TIOCM_DTR); DPRINTF((" DTR")); } if (ISSET(mcr, UMCR_RTS)) { SET(bits, TIOCM_RTS); DPRINTF((" RTS")); } msr = sc->sc_msr; if (ISSET(msr, UMSR_CTS)) { SET(bits, TIOCM_CTS); DPRINTF((" CTS")); } if (ISSET(msr, UMSR_DCD)) { SET(bits, TIOCM_CD); DPRINTF((" CD")); } if (ISSET(msr, UMSR_DSR)) { SET(bits, TIOCM_DSR); DPRINTF((" DSR")); } if (ISSET(msr, UMSR_RI)) { SET(bits, TIOCM_RI); DPRINTF((" RI")); } DPRINTF(("\n")); return (bits); } mcr = 0; if (ISSET(bits, TIOCM_DTR)) SET(mcr, UMCR_DTR); if (ISSET(bits, TIOCM_RTS)) SET(mcr, UMCR_RTS); switch (how) { case DMSET: sc->sc_mcr = mcr; break; case DMBIS: sc->sc_mcr |= mcr; break; case DMBIC: sc->sc_mcr &= ~mcr; break; } onoff = ISSET(sc->sc_mcr, UMCR_DTR) ? 1 : 0; ucom_dtr(sc, onoff); onoff = ISSET(sc->sc_mcr, UMCR_RTS) ? 1 : 0; ucom_rts(sc, onoff); return (0); } Static void ucom_break(struct ucom_softc *sc, int onoff) { DPRINTF(("ucom_break: onoff = %d\n", onoff)); if (sc->sc_callback->ucom_set == NULL) return; sc->sc_callback->ucom_set(sc->sc_parent, sc->sc_portno, UCOM_SET_BREAK, onoff); } Static void ucom_dtr(struct ucom_softc *sc, int onoff) { DPRINTF(("ucom_dtr: onoff = %d\n", onoff)); if (sc->sc_callback->ucom_set == NULL) return; sc->sc_callback->ucom_set(sc->sc_parent, sc->sc_portno, UCOM_SET_DTR, onoff); } Static void ucom_rts(struct ucom_softc *sc, int onoff) { DPRINTF(("ucom_rts: onoff = %d\n", onoff)); if (sc->sc_callback->ucom_set == NULL) return; sc->sc_callback->ucom_set(sc->sc_parent, sc->sc_portno, UCOM_SET_RTS, onoff); } void ucom_status_change(struct ucom_softc *sc) { struct tty *tp = sc->sc_tty; u_char old_msr; int onoff; if (sc->sc_callback->ucom_get_status == NULL) { sc->sc_lsr = 0; sc->sc_msr = 0; return; } old_msr = sc->sc_msr; sc->sc_callback->ucom_get_status(sc->sc_parent, sc->sc_portno, &sc->sc_lsr, &sc->sc_msr); if (ISSET((sc->sc_msr ^ old_msr), UMSR_DCD)) { if (sc->sc_poll == 0) return; onoff = ISSET(sc->sc_msr, UMSR_DCD) ? 1 : 0; DPRINTF(("ucom_status_change: DCD changed to %d\n", onoff)); ttyld_modem(tp, onoff); } } Static int ucomparam(struct tty *tp, struct termios *t) { struct ucom_softc *sc; int error; usbd_status uerr; USB_GET_SC(ucom, UCOMUNIT(tp->t_dev), sc); if (sc->sc_dying) return (EIO); DPRINTF(("ucomparam: sc = %p\n", sc)); /* Check requested parameters. */ if (t->c_ospeed < 0) { DPRINTF(("ucomparam: negative ospeed\n")); return (EINVAL); } if (t->c_ispeed && t->c_ispeed != t->c_ospeed) { DPRINTF(("ucomparam: mismatch ispeed and ospeed\n")); return (EINVAL); } /* * If there were no changes, don't do anything. This avoids dropping * input and improves performance when all we did was frob things like * VMIN and VTIME. */ if (tp->t_ospeed == t->c_ospeed && tp->t_cflag == t->c_cflag) return (0); /* And copy to tty. */ tp->t_ispeed = 0; tp->t_ospeed = t->c_ospeed; tp->t_cflag = t->c_cflag; if (sc->sc_callback->ucom_param == NULL) return (0); ucomstopread(sc); error = sc->sc_callback->ucom_param(sc->sc_parent, sc->sc_portno, t); if (error) { DPRINTF(("ucomparam: callback: error = %d\n", error)); return (error); } ttsetwater(tp); if (t->c_cflag & CRTS_IFLOW) { sc->sc_state |= UCS_RTS_IFLOW; } else if (sc->sc_state & UCS_RTS_IFLOW) { sc->sc_state &= ~UCS_RTS_IFLOW; (void)ucomctl(sc, UMCR_RTS, DMBIS); } - disc_optim(tp, t, sc); + sc->hotchar = ttyldoptim(tp); uerr = ucomstartread(sc); if (uerr != USBD_NORMAL_COMPLETION) return (EIO); return (0); } Static void ucomstart(struct tty *tp) { struct ucom_softc *sc; struct cblock *cbp; usbd_status err; int s; u_char *data; int cnt; USB_GET_SC(ucom, UCOMUNIT(tp->t_dev), sc); DPRINTF(("ucomstart: sc = %p\n", sc)); if (sc->sc_dying) return; s = spltty(); if (tp->t_state & TS_TBLOCK) { if (ISSET(sc->sc_mcr, UMCR_RTS) && ISSET(sc->sc_state, UCS_RTS_IFLOW)) { DPRINTF(("ucomstart: clear RTS\n")); (void)ucomctl(sc, UMCR_RTS, DMBIC); } } else { if (!ISSET(sc->sc_mcr, UMCR_RTS) && tp->t_rawq.c_cc <= tp->t_ilowat && ISSET(sc->sc_state, UCS_RTS_IFLOW)) { DPRINTF(("ucomstart: set RTS\n")); (void)ucomctl(sc, UMCR_RTS, DMBIS); } } if (ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); DPRINTF(("ucomstart: stopped\n")); goto out; } if (tp->t_outq.c_cc <= tp->t_olowat) { if (ISSET(tp->t_state, TS_SO_OLOWAT)) { CLR(tp->t_state, TS_SO_OLOWAT); wakeup(TSA_OLOWAT(tp)); } selwakeuppri(&tp->t_wsel, TTIPRI); if (tp->t_outq.c_cc == 0) { if (ISSET(tp->t_state, TS_BUSY | TS_SO_OCOMPLETE) == TS_SO_OCOMPLETE && tp->t_outq.c_cc == 0) { CLR(tp->t_state, TS_SO_OCOMPLETE); wakeup(TSA_OCOMPLETE(tp)); } goto out; } } /* Grab the first contiguous region of buffer space. */ data = tp->t_outq.c_cf; cbp = (struct cblock *) ((intptr_t) tp->t_outq.c_cf & ~CROUND); cnt = min((char *) (cbp+1) - tp->t_outq.c_cf, tp->t_outq.c_cc); if (cnt == 0) { DPRINTF(("ucomstart: cnt == 0\n")); goto out; } SET(tp->t_state, TS_BUSY); if (cnt > sc->sc_obufsize) { DPRINTF(("ucomstart: big buffer %d chars\n", cnt)); cnt = sc->sc_obufsize; } if (sc->sc_callback->ucom_write != NULL) sc->sc_callback->ucom_write(sc->sc_parent, sc->sc_portno, sc->sc_obuf, data, &cnt); else memcpy(sc->sc_obuf, data, cnt); DPRINTF(("ucomstart: %d chars\n", cnt)); usbd_setup_xfer(sc->sc_oxfer, sc->sc_bulkout_pipe, (usbd_private_handle)sc, sc->sc_obuf, cnt, USBD_NO_COPY, USBD_NO_TIMEOUT, ucomwritecb); /* What can we do on error? */ err = usbd_transfer(sc->sc_oxfer); if (err != USBD_IN_PROGRESS) printf("ucomstart: err=%s\n", usbd_errstr(err)); ttwwakeup(tp); out: splx(s); } Static void ucomstop(struct tty *tp, int flag) { struct ucom_softc *sc; int s; USB_GET_SC(ucom, UCOMUNIT(tp->t_dev), sc); DPRINTF(("ucomstop: %d\n", flag)); if (flag & FREAD) { DPRINTF(("ucomstop: read\n")); ucomstopread(sc); } if (flag & FWRITE) { DPRINTF(("ucomstop: write\n")); s = spltty(); if (ISSET(tp->t_state, TS_BUSY)) { /* XXX do what? */ if (!ISSET(tp->t_state, TS_TTSTOP)) SET(tp->t_state, TS_FLUSH); } splx(s); } DPRINTF(("ucomstop: done\n")); } Static void ucomwritecb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) { struct ucom_softc *sc = (struct ucom_softc *)p; struct tty *tp = sc->sc_tty; u_int32_t cc; int s; DPRINTF(("ucomwritecb: status = %d\n", status)); if (status == USBD_CANCELLED || sc->sc_dying) goto error; if (status != USBD_NORMAL_COMPLETION) { printf("%s: ucomwritecb: %s\n", USBDEVNAME(sc->sc_dev), usbd_errstr(status)); if (status == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_bulkin_pipe); /* XXX we should restart after some delay. */ goto error; } usbd_get_xfer_status(xfer, NULL, NULL, &cc, NULL); DPRINTF(("ucomwritecb: cc = %d\n", cc)); if (cc <= sc->sc_opkthdrlen) { printf("%s: sent size too small, cc = %d\n", USBDEVNAME(sc->sc_dev), cc); goto error; } /* convert from USB bytes to tty bytes */ cc -= sc->sc_opkthdrlen; s = spltty(); CLR(tp->t_state, TS_BUSY); if (ISSET(tp->t_state, TS_FLUSH)) CLR(tp->t_state, TS_FLUSH); else ndflush(&tp->t_outq, cc); ttyld_start(tp); splx(s); return; error: s = spltty(); CLR(tp->t_state, TS_BUSY); splx(s); return; } Static usbd_status ucomstartread(struct ucom_softc *sc) { usbd_status err; DPRINTF(("ucomstartread: start\n")); sc->sc_state &= ~UCS_RXSTOP; if (sc->sc_bulkin_pipe == NULL) return (USBD_NORMAL_COMPLETION); usbd_setup_xfer(sc->sc_ixfer, sc->sc_bulkin_pipe, (usbd_private_handle)sc, sc->sc_ibuf, sc->sc_ibufsize, USBD_SHORT_XFER_OK | USBD_NO_COPY, USBD_NO_TIMEOUT, ucomreadcb); err = usbd_transfer(sc->sc_ixfer); if (err != USBD_IN_PROGRESS) { DPRINTF(("ucomstartread: err = %s\n", usbd_errstr(err))); return (err); } return (USBD_NORMAL_COMPLETION); } Static void ucomreadcb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) { struct ucom_softc *sc = (struct ucom_softc *)p; struct tty *tp = sc->sc_tty; usbd_status err; u_int32_t cc; u_char *cp; int lostcc; int s; DPRINTF(("ucomreadcb: status = %d\n", status)); if (status != USBD_NORMAL_COMPLETION) { if (!(sc->sc_state & UCS_RXSTOP)) printf("%s: ucomreadcb: %s\n", USBDEVNAME(sc->sc_dev), usbd_errstr(status)); if (status == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_bulkin_pipe); /* XXX we should restart after some delay. */ return; } usbd_get_xfer_status(xfer, NULL, (void **)&cp, &cc, NULL); DPRINTF(("ucomreadcb: got %d chars, tp = %p\n", cc, tp)); if (cc == 0) goto resubmit; if (sc->sc_callback->ucom_read != NULL) sc->sc_callback->ucom_read(sc->sc_parent, sc->sc_portno, &cp, &cc); if (cc > sc->sc_ibufsize) { printf("%s: invalid receive data size, %d chars\n", USBDEVNAME(sc->sc_dev), cc); goto resubmit; } if (cc < 1) goto resubmit; s = spltty(); if (tp->t_state & TS_CAN_BYPASS_L_RINT) { if (tp->t_rawq.c_cc + cc > tp->t_ihiwat && (sc->sc_state & UCS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); lostcc = b_to_q((char *)cp, cc, &tp->t_rawq); tp->t_rawcc += cc; ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; ucomstart(tp); } if (lostcc > 0) printf("%s: lost %d chars\n", USBDEVNAME(sc->sc_dev), lostcc); } else { /* Give characters to tty layer. */ while (cc > 0) { DPRINTFN(7, ("ucomreadcb: char = 0x%02x\n", *cp)); if (ttyld_rint(tp, *cp) == -1) { /* XXX what should we do? */ printf("%s: lost %d chars\n", USBDEVNAME(sc->sc_dev), cc); break; } cc--; cp++; } } splx(s); resubmit: err = ucomstartread(sc); if (err) { printf("%s: read start failed\n", USBDEVNAME(sc->sc_dev)); /* XXX what should we dow now? */ } if ((sc->sc_state & UCS_RTS_IFLOW) && !ISSET(sc->sc_mcr, UMCR_RTS) && !(tp->t_state & TS_TBLOCK)) ucomctl(sc, UMCR_RTS, DMBIS); } Static void ucom_cleanup(struct ucom_softc *sc) { DPRINTF(("ucom_cleanup: closing pipes\n")); ucom_shutdown(sc); if (sc->sc_bulkin_pipe != NULL) { usbd_abort_pipe(sc->sc_bulkin_pipe); usbd_close_pipe(sc->sc_bulkin_pipe); sc->sc_bulkin_pipe = NULL; } if (sc->sc_bulkout_pipe != NULL) { usbd_abort_pipe(sc->sc_bulkout_pipe); usbd_close_pipe(sc->sc_bulkout_pipe); sc->sc_bulkout_pipe = NULL; } if (sc->sc_ixfer != NULL) { usbd_free_xfer(sc->sc_ixfer); sc->sc_ixfer = NULL; } if (sc->sc_oxfer != NULL) { usbd_free_xfer(sc->sc_oxfer); sc->sc_oxfer = NULL; } } Static void ucomstopread(struct ucom_softc *sc) { usbd_status err; DPRINTF(("ucomstopread: enter\n")); if (!(sc->sc_state & UCS_RXSTOP)) { sc->sc_state |= UCS_RXSTOP; if (sc->sc_bulkin_pipe == NULL) { DPRINTF(("ucomstopread: bulkin pipe NULL\n")); return; } err = usbd_abort_pipe(sc->sc_bulkin_pipe); if (err) { DPRINTF(("ucomstopread: err = %s\n", usbd_errstr(err))); } } DPRINTF(("ucomstopread: leave\n")); } - -static void -disc_optim(struct tty *tp, struct termios *t, struct ucom_softc *sc) -{ - if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) - && (!(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) { - DPRINTF(("disc_optim: bypass l_rint\n")); - tp->t_state |= TS_CAN_BYPASS_L_RINT; - } else { - DPRINTF(("disc_optim: can't bypass l_rint\n")); - tp->t_state &= ~TS_CAN_BYPASS_L_RINT; - } - sc->hotchar = linesw[tp->t_line].l_hotchar; -} diff --git a/sys/kern/tty.c b/sys/kern/tty.c index d72aec1161a3..da7b696360d6 100644 --- a/sys/kern/tty.c +++ b/sys/kern/tty.c @@ -1,2762 +1,2780 @@ /*- * Copyright (c) 1982, 1986, 1990, 1991, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * Copyright (c) 2002 Networks Associates Technologies, Inc. * All rights reserved. * * Portions of this software were developed for the FreeBSD Project by * ThinkSec AS and NAI Labs, the Security Research Division of Network * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 * ("CBOSS"), as part of the DARPA CHATS research program. * * 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. * 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. * * @(#)tty.c 8.8 (Berkeley) 1/21/94 */ /*- * TODO: * o Fix races for sending the start char in ttyflush(). * o Handle inter-byte timeout for "MIN > 0, TIME > 0" in ttyselect(). * With luck, there will be MIN chars before select() returns(). * o Handle CLOCAL consistently for ptys. Perhaps disallow setting it. * o Don't allow input in TS_ZOMBIE case. It would be visible through * FIONREAD. * o Do the new sio locking stuff here and use it to avoid special * case for EXTPROC? * o Lock PENDIN too? * o Move EXTPROC and/or PENDIN to t_state? * o Wrap most of ttioctl in spltty/splx. * o Implement TIOCNOTTY or remove it from . * o Send STOP if IXOFF is toggled off while TS_TBLOCK is set. * o Don't allow certain termios flags to affect disciplines other * than TTYDISC. Cancel their effects before switch disciplines * and ignore them if they are set while we are in another * discipline. * o Now that historical speed conversions are handled here, don't * do them in drivers. * o Check for TS_CARR_ON being set while everything is closed and not * waiting for carrier. TS_CARR_ON isn't cleared if nothing is open, * so it would live until the next open even if carrier drops. * o Restore TS_WOPEN since it is useful in pstat. It must be cleared * only when _all_ openers leave open(). */ #include __FBSDID("$FreeBSD$"); #include "opt_compat.h" #include "opt_tty.h" #include #include #include #include #include #include #include #if defined(COMPAT_43) || defined(COMPAT_SUNOS) #include #endif #include #define TTYDEFCHARS #include #undef TTYDEFCHARS #include #include #include #include #include #include #include #include #include #include #include #include #include #include MALLOC_DEFINE(M_TTYS, "ttys", "tty data structures"); long tk_cancc; long tk_nin; long tk_nout; long tk_rawcc; static int proc_compare(struct proc *p1, struct proc *p2); static int ttnread(struct tty *tp); static void ttyecho(int c, struct tty *tp); static int ttyoutput(int c, struct tty *tp); static void ttypend(struct tty *tp); static void ttyretype(struct tty *tp); static void ttyrub(int c, struct tty *tp); static void ttyrubo(struct tty *tp, int cnt); static void ttyunblock(struct tty *tp); static int ttywflush(struct tty *tp); static int filt_ttyread(struct knote *kn, long hint); static void filt_ttyrdetach(struct knote *kn); static int filt_ttywrite(struct knote *kn, long hint); static void filt_ttywdetach(struct knote *kn); /* * Table with character classes and parity. The 8th bit indicates parity, * the 7th bit indicates the character is an alphameric or underscore (for * ALTWERASE), and the low 6 bits indicate delay type. If the low 6 bits * are 0 then the character needs no special processing on output; classes * other than 0 might be translated or (not currently) require delays. */ #define E 0x00 /* Even parity. */ #define O 0x80 /* Odd parity. */ #define PARITY(c) (char_type[c] & O) #define ALPHA 0x40 /* Alpha or underscore. */ #define ISALPHA(c) (char_type[(c) & TTY_CHARMASK] & ALPHA) #define CCLASSMASK 0x3f #define CCLASS(c) (char_type[c] & CCLASSMASK) #define BS BACKSPACE #define CC CONTROL #define CR RETURN #define NA ORDINARY | ALPHA #define NL NEWLINE #define NO ORDINARY #define TB TAB #define VT VTAB static u_char const char_type[] = { E|CC, O|CC, O|CC, E|CC, O|CC, E|CC, E|CC, O|CC, /* nul - bel */ O|BS, E|TB, E|NL, O|CC, E|VT, O|CR, O|CC, E|CC, /* bs - si */ O|CC, E|CC, E|CC, O|CC, E|CC, O|CC, O|CC, E|CC, /* dle - etb */ E|CC, O|CC, O|CC, E|CC, O|CC, E|CC, E|CC, O|CC, /* can - us */ O|NO, E|NO, E|NO, O|NO, E|NO, O|NO, O|NO, E|NO, /* sp - ' */ E|NO, O|NO, O|NO, E|NO, O|NO, E|NO, E|NO, O|NO, /* ( - / */ E|NA, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* 0 - 7 */ O|NA, E|NA, E|NO, O|NO, E|NO, O|NO, O|NO, E|NO, /* 8 - ? */ O|NO, E|NA, E|NA, O|NA, E|NA, O|NA, O|NA, E|NA, /* @ - G */ E|NA, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* H - O */ E|NA, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* P - W */ O|NA, E|NA, E|NA, O|NO, E|NO, O|NO, O|NO, O|NA, /* X - _ */ E|NO, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* ` - g */ O|NA, E|NA, E|NA, O|NA, E|NA, O|NA, O|NA, E|NA, /* h - o */ O|NA, E|NA, E|NA, O|NA, E|NA, O|NA, O|NA, E|NA, /* p - w */ E|NA, O|NA, O|NA, E|NO, O|NO, E|NO, E|NO, O|CC, /* x - del */ /* * Meta chars; should be settable per character set; * for now, treat them all as normal characters. */ NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, }; #undef BS #undef CC #undef CR #undef NA #undef NL #undef NO #undef TB #undef VT /* Macros to clear/set/test flags. */ #define SET(t, f) (t) |= (f) #define CLR(t, f) (t) &= ~(f) #define ISSET(t, f) ((t) & (f)) #undef MAX_INPUT /* XXX wrong in */ #define MAX_INPUT TTYHOG /* XXX limit is usually larger for !ICANON */ /* * list of struct tty where pstat(8) can pick it up with sysctl */ static SLIST_HEAD(, tty) tty_list; static int drainwait = 5*60; SYSCTL_INT(_kern, OID_AUTO, drainwait, CTLFLAG_RW, &drainwait, 0, "Output drain timeout in seconds"); /* * Initial open of tty, or (re)entry to standard tty line discipline. */ int ttyopen(dev_t device, struct tty *tp) { int s; s = spltty(); tp->t_dev = device; if (!ISSET(tp->t_state, TS_ISOPEN)) { SET(tp->t_state, TS_ISOPEN); if (ISSET(tp->t_cflag, CLOCAL)) SET(tp->t_state, TS_CONNECTED); bzero(&tp->t_winsize, sizeof(tp->t_winsize)); } /* XXX don't hang forever on output */ if (tp->t_timeout < 0) tp->t_timeout = drainwait*hz; ttsetwater(tp); splx(s); return (0); } /* * Handle close() on a tty line: flush and set to initial state, * bumping generation number so that pending read/write calls * can detect recycling of the tty. * XXX our caller should have done `spltty(); l_close(); ttyclose();' * and l_close() should have flushed, but we repeat the spltty() and * the flush in case there are buggy callers. */ int ttyclose(struct tty *tp) { int s; funsetown(&tp->t_sigio); s = spltty(); if (constty == tp) constty_clear(); ttyflush(tp, FREAD | FWRITE); clist_free_cblocks(&tp->t_canq); clist_free_cblocks(&tp->t_outq); clist_free_cblocks(&tp->t_rawq); tp->t_gen++; tp->t_line = TTYDISC; tp->t_pgrp = NULL; tp->t_session = NULL; tp->t_state = 0; splx(s); return (0); } #define FLUSHQ(q) { \ if ((q)->c_cc) \ ndflush(q, (q)->c_cc); \ } /* Is 'c' a line delimiter ("break" character)? */ #define TTBREAKC(c, lflag) \ ((c) == '\n' || (((c) == cc[VEOF] || \ (c) == cc[VEOL] || ((c) == cc[VEOL2] && lflag & IEXTEN)) && \ (c) != _POSIX_VDISABLE)) /* * Process input of a single character received on a tty. */ int ttyinput(int c, struct tty *tp) { tcflag_t iflag, lflag; cc_t *cc; int i, err; /* * If input is pending take it first. */ lflag = tp->t_lflag; if (ISSET(lflag, PENDIN)) ttypend(tp); /* * Gather stats. */ if (ISSET(lflag, ICANON)) { ++tk_cancc; ++tp->t_cancc; } else { ++tk_rawcc; ++tp->t_rawcc; } ++tk_nin; /* * Block further input iff: * current input > threshold AND input is available to user program * AND input flow control is enabled and not yet invoked. * The 3 is slop for PARMRK. */ iflag = tp->t_iflag; if (tp->t_rawq.c_cc + tp->t_canq.c_cc > tp->t_ihiwat - 3 && (!ISSET(lflag, ICANON) || tp->t_canq.c_cc != 0) && (ISSET(tp->t_cflag, CRTS_IFLOW) || ISSET(iflag, IXOFF)) && !ISSET(tp->t_state, TS_TBLOCK)) ttyblock(tp); /* Handle exceptional conditions (break, parity, framing). */ cc = tp->t_cc; err = (ISSET(c, TTY_ERRORMASK)); if (err) { CLR(c, TTY_ERRORMASK); if (ISSET(err, TTY_BI)) { if (ISSET(iflag, IGNBRK)) return (0); if (ISSET(iflag, BRKINT)) { ttyflush(tp, FREAD | FWRITE); if (tp->t_pgrp != NULL) { PGRP_LOCK(tp->t_pgrp); pgsignal(tp->t_pgrp, SIGINT, 1); PGRP_UNLOCK(tp->t_pgrp); } goto endcase; } if (ISSET(iflag, PARMRK)) goto parmrk; } else if ((ISSET(err, TTY_PE) && ISSET(iflag, INPCK)) || ISSET(err, TTY_FE)) { if (ISSET(iflag, IGNPAR)) return (0); else if (ISSET(iflag, PARMRK)) { parmrk: if (tp->t_rawq.c_cc + tp->t_canq.c_cc > MAX_INPUT - 3) goto input_overflow; (void)putc(0377 | TTY_QUOTE, &tp->t_rawq); (void)putc(0 | TTY_QUOTE, &tp->t_rawq); (void)putc(c | TTY_QUOTE, &tp->t_rawq); goto endcase; } else c = 0; } } if (!ISSET(tp->t_state, TS_TYPEN) && ISSET(iflag, ISTRIP)) CLR(c, 0x80); if (!ISSET(lflag, EXTPROC)) { /* * Check for literal nexting very first */ if (ISSET(tp->t_state, TS_LNCH)) { SET(c, TTY_QUOTE); CLR(tp->t_state, TS_LNCH); } /* * Scan for special characters. This code * is really just a big case statement with * non-constant cases. The bottom of the * case statement is labeled ``endcase'', so goto * it after a case match, or similar. */ /* * Control chars which aren't controlled * by ICANON, ISIG, or IXON. */ if (ISSET(lflag, IEXTEN)) { if (CCEQ(cc[VLNEXT], c)) { if (ISSET(lflag, ECHO)) { if (ISSET(lflag, ECHOE)) { (void)ttyoutput('^', tp); (void)ttyoutput('\b', tp); } else ttyecho(c, tp); } SET(tp->t_state, TS_LNCH); goto endcase; } if (CCEQ(cc[VDISCARD], c)) { if (ISSET(lflag, FLUSHO)) CLR(tp->t_lflag, FLUSHO); else { ttyflush(tp, FWRITE); ttyecho(c, tp); if (tp->t_rawq.c_cc + tp->t_canq.c_cc) ttyretype(tp); SET(tp->t_lflag, FLUSHO); } goto startoutput; } } /* * Signals. */ if (ISSET(lflag, ISIG)) { if (CCEQ(cc[VINTR], c) || CCEQ(cc[VQUIT], c)) { if (!ISSET(lflag, NOFLSH)) ttyflush(tp, FREAD | FWRITE); ttyecho(c, tp); if (tp->t_pgrp != NULL) { PGRP_LOCK(tp->t_pgrp); pgsignal(tp->t_pgrp, CCEQ(cc[VINTR], c) ? SIGINT : SIGQUIT, 1); PGRP_UNLOCK(tp->t_pgrp); } goto endcase; } if (CCEQ(cc[VSUSP], c)) { if (!ISSET(lflag, NOFLSH)) ttyflush(tp, FREAD); ttyecho(c, tp); if (tp->t_pgrp != NULL) { PGRP_LOCK(tp->t_pgrp); pgsignal(tp->t_pgrp, SIGTSTP, 1); PGRP_UNLOCK(tp->t_pgrp); } goto endcase; } } /* * Handle start/stop characters. */ if (ISSET(iflag, IXON)) { if (CCEQ(cc[VSTOP], c)) { if (!ISSET(tp->t_state, TS_TTSTOP)) { SET(tp->t_state, TS_TTSTOP); (*tp->t_stop)(tp, 0); return (0); } if (!CCEQ(cc[VSTART], c)) return (0); /* * if VSTART == VSTOP then toggle */ goto endcase; } if (CCEQ(cc[VSTART], c)) goto restartoutput; } /* * IGNCR, ICRNL, & INLCR */ if (c == '\r') { if (ISSET(iflag, IGNCR)) return (0); else if (ISSET(iflag, ICRNL)) c = '\n'; } else if (c == '\n' && ISSET(iflag, INLCR)) c = '\r'; } if (!ISSET(tp->t_lflag, EXTPROC) && ISSET(lflag, ICANON)) { /* * From here on down canonical mode character * processing takes place. */ /* * erase or erase2 (^H / ^?) */ if (CCEQ(cc[VERASE], c) || CCEQ(cc[VERASE2], c) ) { if (tp->t_rawq.c_cc) ttyrub(unputc(&tp->t_rawq), tp); goto endcase; } /* * kill (^U) */ if (CCEQ(cc[VKILL], c)) { if (ISSET(lflag, ECHOKE) && tp->t_rawq.c_cc == tp->t_rocount && !ISSET(lflag, ECHOPRT)) while (tp->t_rawq.c_cc) ttyrub(unputc(&tp->t_rawq), tp); else { ttyecho(c, tp); if (ISSET(lflag, ECHOK) || ISSET(lflag, ECHOKE)) ttyecho('\n', tp); FLUSHQ(&tp->t_rawq); tp->t_rocount = 0; } CLR(tp->t_state, TS_LOCAL); goto endcase; } /* * word erase (^W) */ if (CCEQ(cc[VWERASE], c) && ISSET(lflag, IEXTEN)) { int ctype; /* * erase whitespace */ while ((c = unputc(&tp->t_rawq)) == ' ' || c == '\t') ttyrub(c, tp); if (c == -1) goto endcase; /* * erase last char of word and remember the * next chars type (for ALTWERASE) */ ttyrub(c, tp); c = unputc(&tp->t_rawq); if (c == -1) goto endcase; if (c == ' ' || c == '\t') { (void)putc(c, &tp->t_rawq); goto endcase; } ctype = ISALPHA(c); /* * erase rest of word */ do { ttyrub(c, tp); c = unputc(&tp->t_rawq); if (c == -1) goto endcase; } while (c != ' ' && c != '\t' && (!ISSET(lflag, ALTWERASE) || ISALPHA(c) == ctype)); (void)putc(c, &tp->t_rawq); goto endcase; } /* * reprint line (^R) */ if (CCEQ(cc[VREPRINT], c) && ISSET(lflag, IEXTEN)) { ttyretype(tp); goto endcase; } /* * ^T - kernel info and generate SIGINFO */ if (CCEQ(cc[VSTATUS], c) && ISSET(lflag, IEXTEN)) { if (ISSET(lflag, ISIG) && tp->t_pgrp != NULL) { PGRP_LOCK(tp->t_pgrp); pgsignal(tp->t_pgrp, SIGINFO, 1); PGRP_UNLOCK(tp->t_pgrp); } if (!ISSET(lflag, NOKERNINFO)) ttyinfo(tp); goto endcase; } } /* * Check for input buffer overflow */ if (tp->t_rawq.c_cc + tp->t_canq.c_cc >= MAX_INPUT) { input_overflow: if (ISSET(iflag, IMAXBEL)) { if (tp->t_outq.c_cc < tp->t_ohiwat) (void)ttyoutput(CTRL('g'), tp); } goto endcase; } if ( c == 0377 && ISSET(iflag, PARMRK) && !ISSET(iflag, ISTRIP) && ISSET(iflag, IGNBRK|IGNPAR) != (IGNBRK|IGNPAR)) (void)putc(0377 | TTY_QUOTE, &tp->t_rawq); /* * Put data char in q for user and * wakeup on seeing a line delimiter. */ if (putc(c, &tp->t_rawq) >= 0) { if (!ISSET(lflag, ICANON)) { ttwakeup(tp); ttyecho(c, tp); goto endcase; } if (TTBREAKC(c, lflag)) { tp->t_rocount = 0; catq(&tp->t_rawq, &tp->t_canq); ttwakeup(tp); } else if (tp->t_rocount++ == 0) tp->t_rocol = tp->t_column; if (ISSET(tp->t_state, TS_ERASE)) { /* * end of prterase \.../ */ CLR(tp->t_state, TS_ERASE); (void)ttyoutput('/', tp); } i = tp->t_column; ttyecho(c, tp); if (CCEQ(cc[VEOF], c) && ISSET(lflag, ECHO)) { /* * Place the cursor over the '^' of the ^D. */ i = imin(2, tp->t_column - i); while (i > 0) { (void)ttyoutput('\b', tp); i--; } } } endcase: /* * IXANY means allow any character to restart output. */ if (ISSET(tp->t_state, TS_TTSTOP) && !ISSET(iflag, IXANY) && cc[VSTART] != cc[VSTOP]) return (0); restartoutput: CLR(tp->t_lflag, FLUSHO); CLR(tp->t_state, TS_TTSTOP); startoutput: return (ttstart(tp)); } /* * Output a single character on a tty, doing output processing * as needed (expanding tabs, newline processing, etc.). * Returns < 0 if succeeds, otherwise returns char to resend. * Must be recursive. */ static int ttyoutput(int c, struct tty *tp) { tcflag_t oflag; int col, s; oflag = tp->t_oflag; if (!ISSET(oflag, OPOST)) { if (ISSET(tp->t_lflag, FLUSHO)) return (-1); if (putc(c, &tp->t_outq)) return (c); tk_nout++; tp->t_outcc++; return (-1); } /* * Do tab expansion if OXTABS is set. Special case if we external * processing, we don't do the tab expansion because we'll probably * get it wrong. If tab expansion needs to be done, let it happen * externally. */ CLR(c, ~TTY_CHARMASK); if (c == '\t' && ISSET(oflag, OXTABS) && !ISSET(tp->t_lflag, EXTPROC)) { c = 8 - (tp->t_column & 7); if (!ISSET(tp->t_lflag, FLUSHO)) { s = spltty(); /* Don't interrupt tabs. */ c -= b_to_q(" ", c, &tp->t_outq); tk_nout += c; tp->t_outcc += c; splx(s); } tp->t_column += c; return (c ? -1 : '\t'); } if (c == CEOT && ISSET(oflag, ONOEOT)) return (-1); /* * Newline translation: if ONLCR is set, * translate newline into "\r\n". */ if (c == '\n' && ISSET(tp->t_oflag, ONLCR)) { tk_nout++; tp->t_outcc++; if (!ISSET(tp->t_lflag, FLUSHO) && putc('\r', &tp->t_outq)) return (c); } /* If OCRNL is set, translate "\r" into "\n". */ else if (c == '\r' && ISSET(tp->t_oflag, OCRNL)) c = '\n'; /* If ONOCR is set, don't transmit CRs when on column 0. */ else if (c == '\r' && ISSET(tp->t_oflag, ONOCR) && tp->t_column == 0) return (-1); tk_nout++; tp->t_outcc++; if (!ISSET(tp->t_lflag, FLUSHO) && putc(c, &tp->t_outq)) return (c); col = tp->t_column; switch (CCLASS(c)) { case BACKSPACE: if (col > 0) --col; break; case CONTROL: break; case NEWLINE: if (ISSET(tp->t_oflag, ONLCR | ONLRET)) col = 0; break; case RETURN: col = 0; break; case ORDINARY: ++col; break; case TAB: col = (col + 8) & ~7; break; } tp->t_column = col; return (-1); } /* * Ioctls for all tty devices. Called after line-discipline specific ioctl * has been called to do discipline-specific functions and/or reject any * of these ioctl commands. */ /* ARGSUSED */ int ttioctl(struct tty *tp, u_long cmd, void *data, int flag) { struct proc *p; struct thread *td; struct pgrp *pgrp; int s, error; td = curthread; /* XXX */ p = td->td_proc; /* If the ioctl involves modification, hang if in the background. */ switch (cmd) { case TIOCCBRK: case TIOCCONS: case TIOCDRAIN: case TIOCEXCL: case TIOCFLUSH: #ifdef TIOCHPCL case TIOCHPCL: #endif case TIOCNXCL: case TIOCSBRK: case TIOCSCTTY: case TIOCSDRAINWAIT: case TIOCSETA: case TIOCSETAF: case TIOCSETAW: case TIOCSETD: case TIOCSPGRP: case TIOCSTART: case TIOCSTAT: case TIOCSTI: case TIOCSTOP: case TIOCSWINSZ: #if defined(COMPAT_43) || defined(COMPAT_SUNOS) case TIOCLBIC: case TIOCLBIS: case TIOCLSET: case TIOCSETC: case OTIOCSETD: case TIOCSETN: case TIOCSETP: case TIOCSLTC: #endif sx_slock(&proctree_lock); PROC_LOCK(p); while (isbackground(p, tp) && !(p->p_flag & P_PPWAIT) && !SIGISMEMBER(p->p_sigacts->ps_sigignore, SIGTTOU) && !SIGISMEMBER(td->td_sigmask, SIGTTOU)) { pgrp = p->p_pgrp; PROC_UNLOCK(p); if (pgrp->pg_jobc == 0) { sx_sunlock(&proctree_lock); return (EIO); } PGRP_LOCK(pgrp); sx_sunlock(&proctree_lock); pgsignal(pgrp, SIGTTOU, 1); PGRP_UNLOCK(pgrp); error = ttysleep(tp, &lbolt, TTOPRI | PCATCH, "ttybg1", 0); if (error) return (error); sx_slock(&proctree_lock); PROC_LOCK(p); } PROC_UNLOCK(p); sx_sunlock(&proctree_lock); break; } switch (cmd) { /* Process the ioctl. */ case FIOASYNC: /* set/clear async i/o */ s = spltty(); if (*(int *)data) SET(tp->t_state, TS_ASYNC); else CLR(tp->t_state, TS_ASYNC); splx(s); break; case FIONBIO: /* set/clear non-blocking i/o */ break; /* XXX: delete. */ case FIONREAD: /* get # bytes to read */ s = spltty(); *(int *)data = ttnread(tp); splx(s); break; case FIOSETOWN: /* * Policy -- Don't allow FIOSETOWN on someone else's * controlling tty */ if (tp->t_session != NULL && !isctty(p, tp)) return (ENOTTY); error = fsetown(*(int *)data, &tp->t_sigio); if (error) return (error); break; case FIOGETOWN: if (tp->t_session != NULL && !isctty(p, tp)) return (ENOTTY); *(int *)data = fgetown(&tp->t_sigio); break; case TIOCEXCL: /* set exclusive use of tty */ s = spltty(); SET(tp->t_state, TS_XCLUDE); splx(s); break; case TIOCFLUSH: { /* flush buffers */ int flags = *(int *)data; if (flags == 0) flags = FREAD | FWRITE; else flags &= FREAD | FWRITE; ttyflush(tp, flags); break; } case TIOCCONS: /* become virtual console */ if (*(int *)data) { struct nameidata nid; if (constty && constty != tp && ISSET(constty->t_state, TS_CONNECTED)) return (EBUSY); /* Ensure user can open the real console. */ NDINIT(&nid, LOOKUP, LOCKLEAF | FOLLOW, UIO_SYSSPACE, "/dev/console", td); if ((error = namei(&nid)) != 0) return (error); NDFREE(&nid, NDF_ONLY_PNBUF); error = VOP_ACCESS(nid.ni_vp, VREAD, td->td_ucred, td); vput(nid.ni_vp); if (error) return (error); constty_set(tp); } else if (tp == constty) constty_clear(); break; case TIOCDRAIN: /* wait till output drained */ error = ttywait(tp); if (error) return (error); break; case TIOCGETA: { /* get termios struct */ struct termios *t = (struct termios *)data; bcopy(&tp->t_termios, t, sizeof(struct termios)); break; } case TIOCGETD: /* get line discipline */ *(int *)data = tp->t_line; break; case TIOCGWINSZ: /* get window size */ *(struct winsize *)data = tp->t_winsize; break; case TIOCGPGRP: /* get pgrp of tty */ if (!isctty(p, tp)) return (ENOTTY); *(int *)data = tp->t_pgrp ? tp->t_pgrp->pg_id : NO_PID; break; #ifdef TIOCHPCL case TIOCHPCL: /* hang up on last close */ s = spltty(); SET(tp->t_cflag, HUPCL); splx(s); break; #endif case TIOCNXCL: /* reset exclusive use of tty */ s = spltty(); CLR(tp->t_state, TS_XCLUDE); splx(s); break; case TIOCOUTQ: /* output queue size */ *(int *)data = tp->t_outq.c_cc; break; case TIOCSETA: /* set termios struct */ case TIOCSETAW: /* drain output, set */ case TIOCSETAF: { /* drn out, fls in, set */ struct termios *t = (struct termios *)data; if (t->c_ispeed == 0) t->c_ispeed = t->c_ospeed; if (t->c_ispeed == 0) t->c_ispeed = tp->t_ospeed; if (t->c_ispeed == 0) return (EINVAL); s = spltty(); if (cmd == TIOCSETAW || cmd == TIOCSETAF) { error = ttywait(tp); if (error) { splx(s); return (error); } if (cmd == TIOCSETAF) ttyflush(tp, FREAD); } if (!ISSET(t->c_cflag, CIGNORE)) { /* * Set device hardware. */ if (tp->t_param && (error = (*tp->t_param)(tp, t))) { splx(s); return (error); } if (ISSET(t->c_cflag, CLOCAL) && !ISSET(tp->t_cflag, CLOCAL)) { /* * XXX disconnections would be too hard to * get rid of without this kludge. The only * way to get rid of controlling terminals * is to exit from the session leader. */ CLR(tp->t_state, TS_ZOMBIE); wakeup(TSA_CARR_ON(tp)); ttwakeup(tp); ttwwakeup(tp); } if ((ISSET(tp->t_state, TS_CARR_ON) || ISSET(t->c_cflag, CLOCAL)) && !ISSET(tp->t_state, TS_ZOMBIE)) SET(tp->t_state, TS_CONNECTED); else CLR(tp->t_state, TS_CONNECTED); tp->t_cflag = t->c_cflag; tp->t_ispeed = t->c_ispeed; if (t->c_ospeed != 0) tp->t_ospeed = t->c_ospeed; ttsetwater(tp); } if (ISSET(t->c_lflag, ICANON) != ISSET(tp->t_lflag, ICANON) && cmd != TIOCSETAF) { if (ISSET(t->c_lflag, ICANON)) SET(tp->t_lflag, PENDIN); else { /* * XXX we really shouldn't allow toggling * ICANON while we're in a non-termios line * discipline. Now we have to worry about * panicing for a null queue. */ if (tp->t_canq.c_cbreserved > 0 && tp->t_rawq.c_cbreserved > 0) { catq(&tp->t_rawq, &tp->t_canq); /* * XXX the queue limits may be * different, so the old queue * swapping method no longer works. */ catq(&tp->t_canq, &tp->t_rawq); } CLR(tp->t_lflag, PENDIN); } ttwakeup(tp); } tp->t_iflag = t->c_iflag; tp->t_oflag = t->c_oflag; /* * Make the EXTPROC bit read only. */ if (ISSET(tp->t_lflag, EXTPROC)) SET(t->c_lflag, EXTPROC); else CLR(t->c_lflag, EXTPROC); tp->t_lflag = t->c_lflag | ISSET(tp->t_lflag, PENDIN); if (t->c_cc[VMIN] != tp->t_cc[VMIN] || t->c_cc[VTIME] != tp->t_cc[VTIME]) ttwakeup(tp); bcopy(t->c_cc, tp->t_cc, sizeof(t->c_cc)); splx(s); break; } case TIOCSETD: { /* set line discipline */ int t = *(int *)data; dev_t device = tp->t_dev; if ((u_int)t >= nlinesw) return (ENXIO); if (t != tp->t_line) { s = spltty(); ttyld_close(tp, flag); error = (*linesw[t].l_open)(device, tp); if (error) { (void)ttyld_open(tp, device); splx(s); return (error); } tp->t_line = t; splx(s); } break; } case TIOCSTART: /* start output, like ^Q */ s = spltty(); if (ISSET(tp->t_state, TS_TTSTOP) || ISSET(tp->t_lflag, FLUSHO)) { CLR(tp->t_lflag, FLUSHO); CLR(tp->t_state, TS_TTSTOP); ttstart(tp); } splx(s); break; case TIOCSTI: /* simulate terminal input */ if ((flag & FREAD) == 0 && suser(td)) return (EPERM); if (!isctty(p, tp) && suser(td)) return (EACCES); s = spltty(); ttyld_rint(tp, *(u_char *)data); splx(s); break; case TIOCSTOP: /* stop output, like ^S */ s = spltty(); if (!ISSET(tp->t_state, TS_TTSTOP)) { SET(tp->t_state, TS_TTSTOP); (*tp->t_stop)(tp, 0); } splx(s); break; case TIOCSCTTY: /* become controlling tty */ /* Session ctty vnode pointer set in vnode layer. */ sx_slock(&proctree_lock); if (!SESS_LEADER(p) || ((p->p_session->s_ttyvp || tp->t_session) && (tp->t_session != p->p_session))) { sx_sunlock(&proctree_lock); return (EPERM); } tp->t_session = p->p_session; tp->t_pgrp = p->p_pgrp; SESS_LOCK(p->p_session); p->p_session->s_ttyp = tp; SESS_UNLOCK(p->p_session); PROC_LOCK(p); p->p_flag |= P_CONTROLT; PROC_UNLOCK(p); sx_sunlock(&proctree_lock); break; case TIOCSPGRP: { /* set pgrp of tty */ sx_slock(&proctree_lock); pgrp = pgfind(*(int *)data); if (!isctty(p, tp)) { if (pgrp != NULL) PGRP_UNLOCK(pgrp); sx_sunlock(&proctree_lock); return (ENOTTY); } if (pgrp == NULL) { sx_sunlock(&proctree_lock); return (EPERM); } PGRP_UNLOCK(pgrp); if (pgrp->pg_session != p->p_session) { sx_sunlock(&proctree_lock); return (EPERM); } sx_sunlock(&proctree_lock); tp->t_pgrp = pgrp; break; } case TIOCSTAT: /* simulate control-T */ s = spltty(); ttyinfo(tp); splx(s); break; case TIOCSWINSZ: /* set window size */ if (bcmp((caddr_t)&tp->t_winsize, data, sizeof (struct winsize))) { tp->t_winsize = *(struct winsize *)data; if (tp->t_pgrp != NULL) { PGRP_LOCK(tp->t_pgrp); pgsignal(tp->t_pgrp, SIGWINCH, 1); PGRP_UNLOCK(tp->t_pgrp); } } break; case TIOCSDRAINWAIT: error = suser(td); if (error) return (error); tp->t_timeout = *(int *)data * hz; wakeup(TSA_OCOMPLETE(tp)); wakeup(TSA_OLOWAT(tp)); break; case TIOCGDRAINWAIT: *(int *)data = tp->t_timeout / hz; break; default: #if defined(COMPAT_43) || defined(COMPAT_SUNOS) return (ttcompat(tp, cmd, data, flag)); #else return (ENOIOCTL); #endif } return (0); } int ttypoll(dev_t dev, int events, struct thread *td) { int s; int revents = 0; struct tty *tp; KASSERT(devsw(dev)->d_flags & D_TTY, ("ttypoll() called on non D_TTY device (%s)", devtoname(dev))); tp = dev->si_tty; KASSERT(tp != NULL, ("ttypoll(): no tty pointer on device (%s)", devtoname(dev))); if (tp == NULL) /* XXX used to return ENXIO, but that means true! */ return ((events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)) | POLLHUP); s = spltty(); if (events & (POLLIN | POLLRDNORM)) { if (ttnread(tp) > 0 || ISSET(tp->t_state, TS_ZOMBIE)) revents |= events & (POLLIN | POLLRDNORM); else selrecord(td, &tp->t_rsel); } if (events & (POLLOUT | POLLWRNORM)) { if ((tp->t_outq.c_cc <= tp->t_olowat && ISSET(tp->t_state, TS_CONNECTED)) || ISSET(tp->t_state, TS_ZOMBIE)) revents |= events & (POLLOUT | POLLWRNORM); else selrecord(td, &tp->t_wsel); } splx(s); return (revents); } static struct filterops ttyread_filtops = { 1, NULL, filt_ttyrdetach, filt_ttyread }; static struct filterops ttywrite_filtops = { 1, NULL, filt_ttywdetach, filt_ttywrite }; int ttykqfilter(dev_t dev, struct knote *kn) { struct tty *tp; struct klist *klist; int s; KASSERT(devsw(dev)->d_flags & D_TTY, ("ttykqfilter() called on non D_TTY device (%s)", devtoname(dev))); tp = dev->si_tty; KASSERT(tp != NULL, ("ttykqfilter(): no tty pointer on device (%s)", devtoname(dev))); switch (kn->kn_filter) { case EVFILT_READ: klist = &tp->t_rsel.si_note; kn->kn_fop = &ttyread_filtops; break; case EVFILT_WRITE: klist = &tp->t_wsel.si_note; kn->kn_fop = &ttywrite_filtops; break; default: return (1); } kn->kn_hook = (caddr_t)dev; s = spltty(); SLIST_INSERT_HEAD(klist, kn, kn_selnext); splx(s); return (0); } static void filt_ttyrdetach(struct knote *kn) { struct tty *tp = ((dev_t)kn->kn_hook)->si_tty; int s = spltty(); SLIST_REMOVE(&tp->t_rsel.si_note, kn, knote, kn_selnext); splx(s); } static int filt_ttyread(struct knote *kn, long hint) { struct tty *tp = ((dev_t)kn->kn_hook)->si_tty; kn->kn_data = ttnread(tp); if (ISSET(tp->t_state, TS_ZOMBIE)) { kn->kn_flags |= EV_EOF; return (1); } return (kn->kn_data > 0); } static void filt_ttywdetach(struct knote *kn) { struct tty *tp = ((dev_t)kn->kn_hook)->si_tty; int s = spltty(); SLIST_REMOVE(&tp->t_wsel.si_note, kn, knote, kn_selnext); splx(s); } static int filt_ttywrite(struct knote *kn, long hint) { struct tty *tp = ((dev_t)kn->kn_hook)->si_tty; kn->kn_data = tp->t_outq.c_cc; if (ISSET(tp->t_state, TS_ZOMBIE)) return (1); return (kn->kn_data <= tp->t_olowat && ISSET(tp->t_state, TS_CONNECTED)); } /* * Must be called at spltty(). */ static int ttnread(struct tty *tp) { int nread; if (ISSET(tp->t_lflag, PENDIN)) ttypend(tp); nread = tp->t_canq.c_cc; if (!ISSET(tp->t_lflag, ICANON)) { nread += tp->t_rawq.c_cc; if (nread < tp->t_cc[VMIN] && tp->t_cc[VTIME] == 0) nread = 0; } return (nread); } /* * Wait for output to drain. */ int ttywait(struct tty *tp) { int error, s; error = 0; s = spltty(); while ((tp->t_outq.c_cc || ISSET(tp->t_state, TS_BUSY)) && ISSET(tp->t_state, TS_CONNECTED) && tp->t_oproc) { (*tp->t_oproc)(tp); if ((tp->t_outq.c_cc || ISSET(tp->t_state, TS_BUSY)) && ISSET(tp->t_state, TS_CONNECTED)) { SET(tp->t_state, TS_SO_OCOMPLETE); error = ttysleep(tp, TSA_OCOMPLETE(tp), TTOPRI | PCATCH, "ttywai", tp->t_timeout); if (error) { if (error == EWOULDBLOCK) error = EIO; break; } } else break; } if (!error && (tp->t_outq.c_cc || ISSET(tp->t_state, TS_BUSY))) error = EIO; splx(s); return (error); } /* * Flush if successfully wait. */ static int ttywflush(struct tty *tp) { int error; if ((error = ttywait(tp)) == 0) ttyflush(tp, FREAD); return (error); } /* * Flush tty read and/or write queues, notifying anyone waiting. */ void ttyflush(struct tty *tp, int rw) { int s; s = spltty(); #if 0 again: #endif if (rw & FWRITE) { FLUSHQ(&tp->t_outq); CLR(tp->t_state, TS_TTSTOP); } (*tp->t_stop)(tp, rw); if (rw & FREAD) { FLUSHQ(&tp->t_canq); FLUSHQ(&tp->t_rawq); CLR(tp->t_lflag, PENDIN); tp->t_rocount = 0; tp->t_rocol = 0; CLR(tp->t_state, TS_LOCAL); ttwakeup(tp); if (ISSET(tp->t_state, TS_TBLOCK)) { if (rw & FWRITE) FLUSHQ(&tp->t_outq); ttyunblock(tp); /* * Don't let leave any state that might clobber the * next line discipline (although we should do more * to send the START char). Not clearing the state * may have caused the "putc to a clist with no * reserved cblocks" panic/printf. */ CLR(tp->t_state, TS_TBLOCK); #if 0 /* forget it, sleeping isn't always safe and we don't know when it is */ if (ISSET(tp->t_iflag, IXOFF)) { /* * XXX wait a bit in the hope that the stop * character (if any) will go out. Waiting * isn't good since it allows races. This * will be fixed when the stop character is * put in a special queue. Don't bother with * the checks in ttywait() since the timeout * will save us. */ SET(tp->t_state, TS_SO_OCOMPLETE); ttysleep(tp, TSA_OCOMPLETE(tp), TTOPRI, "ttyfls", hz / 10); /* * Don't try sending the stop character again. */ CLR(tp->t_state, TS_TBLOCK); goto again; } #endif } } if (rw & FWRITE) { FLUSHQ(&tp->t_outq); ttwwakeup(tp); } splx(s); } /* * Copy in the default termios characters. */ void termioschars(struct termios *t) { bcopy(ttydefchars, t->c_cc, sizeof t->c_cc); } /* * Old interface. */ void ttychars(struct tty *tp) { termioschars(&tp->t_termios); } /* * Handle input high water. Send stop character for the IXOFF case. Turn * on our input flow control bit and propagate the changes to the driver. * XXX the stop character should be put in a special high priority queue. */ void ttyblock(struct tty *tp) { SET(tp->t_state, TS_TBLOCK); if (ISSET(tp->t_iflag, IXOFF) && tp->t_cc[VSTOP] != _POSIX_VDISABLE && putc(tp->t_cc[VSTOP], &tp->t_outq) != 0) CLR(tp->t_state, TS_TBLOCK); /* try again later */ ttstart(tp); } /* * Handle input low water. Send start character for the IXOFF case. Turn * off our input flow control bit and propagate the changes to the driver. * XXX the start character should be put in a special high priority queue. */ static void ttyunblock(struct tty *tp) { CLR(tp->t_state, TS_TBLOCK); if (ISSET(tp->t_iflag, IXOFF) && tp->t_cc[VSTART] != _POSIX_VDISABLE && putc(tp->t_cc[VSTART], &tp->t_outq) != 0) SET(tp->t_state, TS_TBLOCK); /* try again later */ ttstart(tp); } #ifdef notyet /* Not used by any current (i386) drivers. */ /* * Restart after an inter-char delay. */ void ttrstrt(void *tp_arg) { struct tty *tp; int s; KASSERT(tp_arg != NULL, ("ttrstrt")); tp = tp_arg; s = spltty(); CLR(tp->t_state, TS_TIMEOUT); ttstart(tp); splx(s); } #endif int ttstart(struct tty *tp) { if (tp->t_oproc != NULL) /* XXX: Kludge for pty. */ (*tp->t_oproc)(tp); return (0); } /* * "close" a line discipline */ int ttylclose(struct tty *tp, int flag) { if (flag & FNONBLOCK || ttywflush(tp)) ttyflush(tp, FREAD | FWRITE); return (0); } /* * Handle modem control transition on a tty. * Flag indicates new state of carrier. * Returns 0 if the line should be turned off, otherwise 1. */ int ttymodem(struct tty *tp, int flag) { if (ISSET(tp->t_state, TS_CARR_ON) && ISSET(tp->t_cflag, MDMBUF)) { /* * MDMBUF: do flow control according to carrier flag * XXX TS_CAR_OFLOW doesn't do anything yet. TS_TTSTOP * works if IXON and IXANY are clear. */ if (flag) { CLR(tp->t_state, TS_CAR_OFLOW); CLR(tp->t_state, TS_TTSTOP); ttstart(tp); } else if (!ISSET(tp->t_state, TS_CAR_OFLOW)) { SET(tp->t_state, TS_CAR_OFLOW); SET(tp->t_state, TS_TTSTOP); (*tp->t_stop)(tp, 0); } } else if (flag == 0) { /* * Lost carrier. */ CLR(tp->t_state, TS_CARR_ON); if (ISSET(tp->t_state, TS_ISOPEN) && !ISSET(tp->t_cflag, CLOCAL)) { SET(tp->t_state, TS_ZOMBIE); CLR(tp->t_state, TS_CONNECTED); if (tp->t_session) { sx_slock(&proctree_lock); if (tp->t_session->s_leader) { struct proc *p; p = tp->t_session->s_leader; PROC_LOCK(p); psignal(p, SIGHUP); PROC_UNLOCK(p); } sx_sunlock(&proctree_lock); } ttyflush(tp, FREAD | FWRITE); return (0); } } else { /* * Carrier now on. */ SET(tp->t_state, TS_CARR_ON); if (!ISSET(tp->t_state, TS_ZOMBIE)) SET(tp->t_state, TS_CONNECTED); wakeup(TSA_CARR_ON(tp)); ttwakeup(tp); ttwwakeup(tp); } return (1); } /* * Reinput pending characters after state switch * call at spltty(). */ static void ttypend(struct tty *tp) { struct clist tq; int c; CLR(tp->t_lflag, PENDIN); SET(tp->t_state, TS_TYPEN); /* * XXX this assumes too much about clist internals. It may even * fail if the cblock slush pool is empty. We can't allocate more * cblocks here because we are called from an interrupt handler * and clist_alloc_cblocks() can wait. */ tq = tp->t_rawq; bzero(&tp->t_rawq, sizeof tp->t_rawq); tp->t_rawq.c_cbmax = tq.c_cbmax; tp->t_rawq.c_cbreserved = tq.c_cbreserved; while ((c = getc(&tq)) >= 0) ttyinput(c, tp); CLR(tp->t_state, TS_TYPEN); } /* * Process a read call on a tty device. */ int ttread(struct tty *tp, struct uio *uio, int flag) { struct clist *qp; int c; tcflag_t lflag; cc_t *cc = tp->t_cc; struct thread *td; struct proc *p; int s, first, error = 0; int has_stime = 0, last_cc = 0; long slp = 0; /* XXX this should be renamed `timo'. */ struct timeval stime; struct pgrp *pg; td = curthread; p = td->td_proc; loop: s = spltty(); lflag = tp->t_lflag; /* * take pending input first */ if (ISSET(lflag, PENDIN)) { ttypend(tp); splx(s); /* reduce latency */ s = spltty(); lflag = tp->t_lflag; /* XXX ttypend() clobbers it */ } /* * Hang process if it's in the background. */ if (isbackground(p, tp)) { splx(s); sx_slock(&proctree_lock); PROC_LOCK(p); if (SIGISMEMBER(p->p_sigacts->ps_sigignore, SIGTTIN) || SIGISMEMBER(td->td_sigmask, SIGTTIN) || (p->p_flag & P_PPWAIT) || p->p_pgrp->pg_jobc == 0) { PROC_UNLOCK(p); sx_sunlock(&proctree_lock); return (EIO); } pg = p->p_pgrp; PROC_UNLOCK(p); PGRP_LOCK(pg); sx_sunlock(&proctree_lock); pgsignal(pg, SIGTTIN, 1); PGRP_UNLOCK(pg); error = ttysleep(tp, &lbolt, TTIPRI | PCATCH, "ttybg2", 0); if (error) return (error); goto loop; } if (ISSET(tp->t_state, TS_ZOMBIE)) { splx(s); return (0); /* EOF */ } /* * If canonical, use the canonical queue, * else use the raw queue. * * (should get rid of clists...) */ qp = ISSET(lflag, ICANON) ? &tp->t_canq : &tp->t_rawq; if (flag & IO_NDELAY) { if (qp->c_cc > 0) goto read; if (!ISSET(lflag, ICANON) && cc[VMIN] == 0) { splx(s); return (0); } splx(s); return (EWOULDBLOCK); } if (!ISSET(lflag, ICANON)) { int m = cc[VMIN]; long t = cc[VTIME]; struct timeval timecopy; /* * Check each of the four combinations. * (m > 0 && t == 0) is the normal read case. * It should be fairly efficient, so we check that and its * companion case (m == 0 && t == 0) first. * For the other two cases, we compute the target sleep time * into slp. */ if (t == 0) { if (qp->c_cc < m) goto sleep; if (qp->c_cc > 0) goto read; /* m, t and qp->c_cc are all 0. 0 is enough input. */ splx(s); return (0); } t *= 100000; /* time in us */ #define diff(t1, t2) (((t1).tv_sec - (t2).tv_sec) * 1000000 + \ ((t1).tv_usec - (t2).tv_usec)) if (m > 0) { if (qp->c_cc <= 0) goto sleep; if (qp->c_cc >= m) goto read; getmicrotime(&timecopy); if (!has_stime) { /* first character, start timer */ has_stime = 1; stime = timecopy; slp = t; } else if (qp->c_cc > last_cc) { /* got a character, restart timer */ stime = timecopy; slp = t; } else { /* nothing, check expiration */ slp = t - diff(timecopy, stime); if (slp <= 0) goto read; } last_cc = qp->c_cc; } else { /* m == 0 */ if (qp->c_cc > 0) goto read; getmicrotime(&timecopy); if (!has_stime) { has_stime = 1; stime = timecopy; slp = t; } else { slp = t - diff(timecopy, stime); if (slp <= 0) { /* Timed out, but 0 is enough input. */ splx(s); return (0); } } } #undef diff /* * Rounding down may make us wake up just short * of the target, so we round up. * The formula is ceiling(slp * hz/1000000). * 32-bit arithmetic is enough for hz < 169. * XXX see tvtohz() for how to avoid overflow if hz * is large (divide by `tick' and/or arrange to * use tvtohz() if hz is large). */ slp = (long) (((u_long)slp * hz) + 999999) / 1000000; goto sleep; } if (qp->c_cc <= 0) { sleep: /* * There is no input, or not enough input and we can block. */ error = ttysleep(tp, TSA_HUP_OR_INPUT(tp), TTIPRI | PCATCH, ISSET(tp->t_state, TS_CONNECTED) ? "ttyin" : "ttyhup", (int)slp); splx(s); if (error == EWOULDBLOCK) error = 0; else if (error) return (error); /* * XXX what happens if another process eats some input * while we are asleep (not just here)? It would be * safest to detect changes and reset our state variables * (has_stime and last_cc). */ slp = 0; goto loop; } read: splx(s); /* * Input present, check for input mapping and processing. */ first = 1; if (ISSET(lflag, ICANON | ISIG)) goto slowcase; for (;;) { char ibuf[IBUFSIZ]; int icc; icc = imin(uio->uio_resid, IBUFSIZ); icc = q_to_b(qp, ibuf, icc); if (icc <= 0) { if (first) goto loop; break; } error = uiomove(ibuf, icc, uio); /* * XXX if there was an error then we should ungetc() the * unmoved chars and reduce icc here. */ if (error) break; if (uio->uio_resid == 0) break; first = 0; } goto out; slowcase: for (;;) { c = getc(qp); if (c < 0) { if (first) goto loop; break; } /* * delayed suspend (^Y) */ if (CCEQ(cc[VDSUSP], c) && ISSET(lflag, IEXTEN | ISIG) == (IEXTEN | ISIG)) { if (tp->t_pgrp != NULL) { PGRP_LOCK(tp->t_pgrp); pgsignal(tp->t_pgrp, SIGTSTP, 1); PGRP_UNLOCK(tp->t_pgrp); } if (first) { error = ttysleep(tp, &lbolt, TTIPRI | PCATCH, "ttybg3", 0); if (error) break; goto loop; } break; } /* * Interpret EOF only in canonical mode. */ if (CCEQ(cc[VEOF], c) && ISSET(lflag, ICANON)) break; /* * Give user character. */ error = ureadc(c, uio); if (error) /* XXX should ungetc(c, qp). */ break; if (uio->uio_resid == 0) break; /* * In canonical mode check for a "break character" * marking the end of a "line of input". */ if (ISSET(lflag, ICANON) && TTBREAKC(c, lflag)) break; first = 0; } out: /* * Look to unblock input now that (presumably) * the input queue has gone down. */ s = spltty(); if (ISSET(tp->t_state, TS_TBLOCK) && tp->t_rawq.c_cc + tp->t_canq.c_cc <= tp->t_ilowat) ttyunblock(tp); splx(s); return (error); } /* * Check the output queue on tp for space for a kernel message (from uprintf * or tprintf). Allow some space over the normal hiwater mark so we don't * lose messages due to normal flow control, but don't let the tty run amok. * Sleeps here are not interruptible, but we return prematurely if new signals * arrive. */ int ttycheckoutq(struct tty *tp, int wait) { int hiwat, s; sigset_t oldmask; struct thread *td; struct proc *p; td = curthread; p = td->td_proc; hiwat = tp->t_ohiwat; SIGEMPTYSET(oldmask); s = spltty(); if (wait) { PROC_LOCK(p); oldmask = td->td_siglist; PROC_UNLOCK(p); } if (tp->t_outq.c_cc > hiwat + OBUFSIZ + 100) while (tp->t_outq.c_cc > hiwat) { ttstart(tp); if (tp->t_outq.c_cc <= hiwat) break; if (!wait) { splx(s); return (0); } PROC_LOCK(p); if (!SIGSETEQ(td->td_siglist, oldmask)) { PROC_UNLOCK(p); splx(s); return (0); } PROC_UNLOCK(p); SET(tp->t_state, TS_SO_OLOWAT); tsleep(TSA_OLOWAT(tp), PZERO - 1, "ttoutq", hz); } splx(s); return (1); } /* * Process a write call on a tty device. */ int ttwrite(struct tty *tp, struct uio *uio, int flag) { char *cp = NULL; int cc, ce; struct thread *td; struct proc *p; int i, hiwat, cnt, error, s; char obuf[OBUFSIZ]; hiwat = tp->t_ohiwat; cnt = uio->uio_resid; error = 0; cc = 0; td = curthread; p = td->td_proc; loop: s = spltty(); if (ISSET(tp->t_state, TS_ZOMBIE)) { splx(s); if (uio->uio_resid == cnt) error = EIO; goto out; } if (!ISSET(tp->t_state, TS_CONNECTED)) { if (flag & IO_NDELAY) { splx(s); error = EWOULDBLOCK; goto out; } error = ttysleep(tp, TSA_CARR_ON(tp), TTIPRI | PCATCH, "ttydcd", 0); splx(s); if (error) goto out; goto loop; } splx(s); /* * Hang the process if it's in the background. */ sx_slock(&proctree_lock); PROC_LOCK(p); if (isbackground(p, tp) && ISSET(tp->t_lflag, TOSTOP) && !(p->p_flag & P_PPWAIT) && !SIGISMEMBER(p->p_sigacts->ps_sigignore, SIGTTOU) && !SIGISMEMBER(td->td_sigmask, SIGTTOU)) { if (p->p_pgrp->pg_jobc == 0) { PROC_UNLOCK(p); sx_sunlock(&proctree_lock); error = EIO; goto out; } PROC_UNLOCK(p); PGRP_LOCK(p->p_pgrp); sx_sunlock(&proctree_lock); pgsignal(p->p_pgrp, SIGTTOU, 1); PGRP_UNLOCK(p->p_pgrp); error = ttysleep(tp, &lbolt, TTIPRI | PCATCH, "ttybg4", 0); if (error) goto out; goto loop; } else { PROC_UNLOCK(p); sx_sunlock(&proctree_lock); } /* * Process the user's data in at most OBUFSIZ chunks. Perform any * output translation. Keep track of high water mark, sleep on * overflow awaiting device aid in acquiring new space. */ while (uio->uio_resid > 0 || cc > 0) { if (ISSET(tp->t_lflag, FLUSHO)) { uio->uio_resid = 0; return (0); } if (tp->t_outq.c_cc > hiwat) goto ovhiwat; /* * Grab a hunk of data from the user, unless we have some * leftover from last time. */ if (cc == 0) { cc = imin(uio->uio_resid, OBUFSIZ); cp = obuf; error = uiomove(cp, cc, uio); if (error) { cc = 0; break; } } /* * If nothing fancy need be done, grab those characters we * can handle without any of ttyoutput's processing and * just transfer them to the output q. For those chars * which require special processing (as indicated by the * bits in char_type), call ttyoutput. After processing * a hunk of data, look for FLUSHO so ^O's will take effect * immediately. */ while (cc > 0) { if (!ISSET(tp->t_oflag, OPOST)) ce = cc; else { ce = cc - scanc((u_int)cc, (u_char *)cp, char_type, CCLASSMASK); /* * If ce is zero, then we're processing * a special character through ttyoutput. */ if (ce == 0) { tp->t_rocount = 0; if (ttyoutput(*cp, tp) >= 0) { /* No Clists, wait a bit. */ ttstart(tp); if (flag & IO_NDELAY) { error = EWOULDBLOCK; goto out; } error = ttysleep(tp, &lbolt, TTOPRI|PCATCH, "ttybf1", 0); if (error) goto out; goto loop; } cp++; cc--; if (ISSET(tp->t_lflag, FLUSHO) || tp->t_outq.c_cc > hiwat) goto ovhiwat; continue; } } /* * A bunch of normal characters have been found. * Transfer them en masse to the output queue and * continue processing at the top of the loop. * If there are any further characters in this * <= OBUFSIZ chunk, the first should be a character * requiring special handling by ttyoutput. */ tp->t_rocount = 0; i = b_to_q(cp, ce, &tp->t_outq); ce -= i; tp->t_column += ce; cp += ce, cc -= ce, tk_nout += ce; tp->t_outcc += ce; if (i > 0) { /* No Clists, wait a bit. */ ttstart(tp); if (flag & IO_NDELAY) { error = EWOULDBLOCK; goto out; } error = ttysleep(tp, &lbolt, TTOPRI | PCATCH, "ttybf2", 0); if (error) goto out; goto loop; } if (ISSET(tp->t_lflag, FLUSHO) || tp->t_outq.c_cc > hiwat) break; } ttstart(tp); } out: /* * If cc is nonzero, we leave the uio structure inconsistent, as the * offset and iov pointers have moved forward, but it doesn't matter * (the call will either return short or restart with a new uio). */ uio->uio_resid += cc; return (error); ovhiwat: ttstart(tp); s = spltty(); /* * This can only occur if FLUSHO is set in t_lflag, * or if ttstart/oproc is synchronous (or very fast). */ if (tp->t_outq.c_cc <= hiwat) { splx(s); goto loop; } if (flag & IO_NDELAY) { splx(s); uio->uio_resid += cc; return (uio->uio_resid == cnt ? EWOULDBLOCK : 0); } SET(tp->t_state, TS_SO_OLOWAT); error = ttysleep(tp, TSA_OLOWAT(tp), TTOPRI | PCATCH, "ttywri", tp->t_timeout); splx(s); if (error == EWOULDBLOCK) error = EIO; if (error) goto out; goto loop; } /* * Rubout one character from the rawq of tp * as cleanly as possible. */ static void ttyrub(int c, struct tty *tp) { char *cp; int savecol; int tabc, s; if (!ISSET(tp->t_lflag, ECHO) || ISSET(tp->t_lflag, EXTPROC)) return; CLR(tp->t_lflag, FLUSHO); if (ISSET(tp->t_lflag, ECHOE)) { if (tp->t_rocount == 0) { /* * Screwed by ttwrite; retype */ ttyretype(tp); return; } if (c == ('\t' | TTY_QUOTE) || c == ('\n' | TTY_QUOTE)) ttyrubo(tp, 2); else { CLR(c, ~TTY_CHARMASK); switch (CCLASS(c)) { case ORDINARY: ttyrubo(tp, 1); break; case BACKSPACE: case CONTROL: case NEWLINE: case RETURN: case VTAB: if (ISSET(tp->t_lflag, ECHOCTL)) ttyrubo(tp, 2); break; case TAB: if (tp->t_rocount < tp->t_rawq.c_cc) { ttyretype(tp); return; } s = spltty(); savecol = tp->t_column; SET(tp->t_state, TS_CNTTB); SET(tp->t_lflag, FLUSHO); tp->t_column = tp->t_rocol; cp = tp->t_rawq.c_cf; if (cp) tabc = *cp; /* XXX FIX NEXTC */ for (; cp; cp = nextc(&tp->t_rawq, cp, &tabc)) ttyecho(tabc, tp); CLR(tp->t_lflag, FLUSHO); CLR(tp->t_state, TS_CNTTB); splx(s); /* savecol will now be length of the tab. */ savecol -= tp->t_column; tp->t_column += savecol; if (savecol > 8) savecol = 8; /* overflow screw */ while (--savecol >= 0) (void)ttyoutput('\b', tp); break; default: /* XXX */ #define PANICSTR "ttyrub: would panic c = %d, val = %d\n" (void)printf(PANICSTR, c, CCLASS(c)); #ifdef notdef panic(PANICSTR, c, CCLASS(c)); #endif } } } else if (ISSET(tp->t_lflag, ECHOPRT)) { if (!ISSET(tp->t_state, TS_ERASE)) { SET(tp->t_state, TS_ERASE); (void)ttyoutput('\\', tp); } ttyecho(c, tp); } else { ttyecho(tp->t_cc[VERASE], tp); /* * This code may be executed not only when an ERASE key * is pressed, but also when ^U (KILL) or ^W (WERASE) are. * So, I didn't think it was worthwhile to pass the extra * information (which would need an extra parameter, * changing every call) needed to distinguish the ERASE2 * case from the ERASE. */ } --tp->t_rocount; } /* * Back over cnt characters, erasing them. */ static void ttyrubo(struct tty *tp, int cnt) { while (cnt-- > 0) { (void)ttyoutput('\b', tp); (void)ttyoutput(' ', tp); (void)ttyoutput('\b', tp); } } /* * ttyretype -- * Reprint the rawq line. Note, it is assumed that c_cc has already * been checked. */ static void ttyretype(struct tty *tp) { char *cp; int s, c; /* Echo the reprint character. */ if (tp->t_cc[VREPRINT] != _POSIX_VDISABLE) ttyecho(tp->t_cc[VREPRINT], tp); (void)ttyoutput('\n', tp); /* * XXX * FIX: NEXTC IS BROKEN - DOESN'T CHECK QUOTE * BIT OF FIRST CHAR. */ s = spltty(); for (cp = tp->t_canq.c_cf, c = (cp != NULL ? *cp : 0); cp != NULL; cp = nextc(&tp->t_canq, cp, &c)) ttyecho(c, tp); for (cp = tp->t_rawq.c_cf, c = (cp != NULL ? *cp : 0); cp != NULL; cp = nextc(&tp->t_rawq, cp, &c)) ttyecho(c, tp); CLR(tp->t_state, TS_ERASE); splx(s); tp->t_rocount = tp->t_rawq.c_cc; tp->t_rocol = 0; } /* * Echo a typed character to the terminal. */ static void ttyecho(int c, struct tty *tp) { if (!ISSET(tp->t_state, TS_CNTTB)) CLR(tp->t_lflag, FLUSHO); if ((!ISSET(tp->t_lflag, ECHO) && (c != '\n' || !ISSET(tp->t_lflag, ECHONL))) || ISSET(tp->t_lflag, EXTPROC)) return; if (ISSET(tp->t_lflag, ECHOCTL) && ((ISSET(c, TTY_CHARMASK) <= 037 && c != '\t' && c != '\n') || ISSET(c, TTY_CHARMASK) == 0177)) { (void)ttyoutput('^', tp); CLR(c, ~TTY_CHARMASK); if (c == 0177) c = '?'; else c += 'A' - 1; } (void)ttyoutput(c, tp); } /* * Wake up any readers on a tty. */ void ttwakeup(struct tty *tp) { if (SEL_WAITING(&tp->t_rsel)) selwakeuppri(&tp->t_rsel, TTIPRI); if (ISSET(tp->t_state, TS_ASYNC) && tp->t_sigio != NULL) pgsigio(&tp->t_sigio, SIGIO, (tp->t_session != NULL)); wakeup(TSA_HUP_OR_INPUT(tp)); KNOTE(&tp->t_rsel.si_note, 0); } /* * Wake up any writers on a tty. */ void ttwwakeup(struct tty *tp) { if (SEL_WAITING(&tp->t_wsel) && tp->t_outq.c_cc <= tp->t_olowat) selwakeuppri(&tp->t_wsel, TTOPRI); if (ISSET(tp->t_state, TS_ASYNC) && tp->t_sigio != NULL) pgsigio(&tp->t_sigio, SIGIO, (tp->t_session != NULL)); if (ISSET(tp->t_state, TS_BUSY | TS_SO_OCOMPLETE) == TS_SO_OCOMPLETE && tp->t_outq.c_cc == 0) { CLR(tp->t_state, TS_SO_OCOMPLETE); wakeup(TSA_OCOMPLETE(tp)); } if (ISSET(tp->t_state, TS_SO_OLOWAT) && tp->t_outq.c_cc <= tp->t_olowat) { CLR(tp->t_state, TS_SO_OLOWAT); wakeup(TSA_OLOWAT(tp)); } KNOTE(&tp->t_wsel.si_note, 0); } /* * Look up a code for a specified speed in a conversion table; * used by drivers to map software speed values to hardware parameters. */ int ttspeedtab(int speed, struct speedtab *table) { for ( ; table->sp_speed != -1; table++) if (table->sp_speed == speed) return (table->sp_code); return (-1); } /* * Set input and output watermarks and buffer sizes. For input, the * high watermark is about one second's worth of input above empty, the * low watermark is slightly below high water, and the buffer size is a * driver-dependent amount above high water. For output, the watermarks * are near the ends of the buffer, with about 1 second's worth of input * between them. All this only applies to the standard line discipline. */ void ttsetwater(struct tty *tp) { int cps, ttmaxhiwat, x; /* Input. */ clist_alloc_cblocks(&tp->t_canq, TTYHOG, 512); switch (tp->t_ispeedwat) { case (speed_t)-1: cps = tp->t_ispeed / 10; break; case 0: /* * This case is for old drivers that don't know about * t_ispeedwat. Arrange for them to get the old buffer * sizes and watermarks. */ cps = TTYHOG - 2 * 256; tp->t_ififosize = 2 * 256; break; default: cps = tp->t_ispeedwat / 10; break; } tp->t_ihiwat = cps; tp->t_ilowat = 7 * cps / 8; x = cps + tp->t_ififosize; clist_alloc_cblocks(&tp->t_rawq, x, x); /* Output. */ switch (tp->t_ospeedwat) { case (speed_t)-1: cps = tp->t_ospeed / 10; ttmaxhiwat = 2 * TTMAXHIWAT; break; case 0: cps = tp->t_ospeed / 10; ttmaxhiwat = TTMAXHIWAT; break; default: cps = tp->t_ospeedwat / 10; ttmaxhiwat = 8 * TTMAXHIWAT; break; } #define CLAMP(x, h, l) ((x) > h ? h : ((x) < l) ? l : (x)) tp->t_olowat = x = CLAMP(cps / 2, TTMAXLOWAT, TTMINLOWAT); x += cps; x = CLAMP(x, ttmaxhiwat, TTMINHIWAT); /* XXX clamps are too magic */ tp->t_ohiwat = roundup(x, CBSIZE); /* XXX for compat */ x = imax(tp->t_ohiwat, TTMAXHIWAT); /* XXX for compat/safety */ x += OBUFSIZ + 100; clist_alloc_cblocks(&tp->t_outq, x, x); #undef CLAMP } /* * Report on state of foreground process group. */ void ttyinfo(struct tty *tp) { struct timeval utime, stime; struct proc *p, *pick; struct thread *td; const char *stateprefix, *state; long rss; int load, pctcpu; if (ttycheckoutq(tp,0) == 0) return; /* Print load average. */ load = (averunnable.ldavg[0] * 100 + FSCALE / 2) >> FSHIFT; ttyprintf(tp, "load: %d.%02d ", load / 100, load % 100); /* * On return following a ttyprintf(), we set tp->t_rocount to 0 so * that pending input will be retyped on BS. */ if (tp->t_session == NULL) { ttyprintf(tp, "not a controlling terminal\n"); tp->t_rocount = 0; return; } if (tp->t_pgrp == NULL) { ttyprintf(tp, "no foreground process group\n"); tp->t_rocount = 0; return; } PGRP_LOCK(tp->t_pgrp); if ((p = LIST_FIRST(&tp->t_pgrp->pg_members)) == 0) { PGRP_UNLOCK(tp->t_pgrp); ttyprintf(tp, "empty foreground process group\n"); tp->t_rocount = 0; return; } /* * Pick the most interesting process and copy some of its * state for printing later. sched_lock must be held for * most parts of this. Holding it throughout is simplest * and prevents even unimportant inconsistencies in the * copy of the state, but may increase interrupt latency * too much. */ mtx_lock_spin(&sched_lock); for (pick = NULL; p != 0; p = LIST_NEXT(p, p_pglist)) if (proc_compare(pick, p)) pick = p; PGRP_UNLOCK(tp->t_pgrp); td = FIRST_THREAD_IN_PROC(pick); /* XXXKSE */ #if 0 KASSERT(td != NULL, ("ttyinfo: no thread")); #else if (td == NULL) { mtx_unlock_spin(&sched_lock); ttyprintf(tp, "foreground process without thread\n"); tp->t_rocount = 0; return; } #endif stateprefix = ""; if (TD_IS_RUNNING(td)) state = "running"; else if (TD_ON_RUNQ(td) || TD_CAN_RUN(td)) state = "runnable"; else if (TD_IS_SLEEPING(td)) { /* XXX: If we're sleeping, are we ever not in a queue? */ if (TD_ON_SLEEPQ(td)) state = td->td_wmesg; else state = "sleeping without queue"; } else if (TD_ON_LOCK(td)) { state = td->td_lockname; stateprefix = "*"; } else if (TD_IS_SUSPENDED(td)) state = "suspended"; else if (TD_AWAITING_INTR(td)) state = "intrwait"; else state = "unknown"; calcru(pick, &utime, &stime, NULL); pctcpu = (sched_pctcpu(td) * 10000 + FSCALE / 2) >> FSHIFT; if (pick->p_state == PRS_NEW || pick->p_state == PRS_ZOMBIE) rss = 0; else rss = pgtok(vmspace_resident_count(pick->p_vmspace)); mtx_unlock_spin(&sched_lock); /* Print command, pid, state, utime, stime, %cpu, and rss. */ ttyprintf(tp, " cmd: %s %d [%s%s] %ld.%02ldu %ld.%02lds %d%% %ldk\n", pick->p_comm, pick->p_pid, stateprefix, state, (long)utime.tv_sec, utime.tv_usec / 10000, (long)stime.tv_sec, stime.tv_usec / 10000, pctcpu / 100, rss); tp->t_rocount = 0; } /* * Returns 1 if p2 is "better" than p1 * * The algorithm for picking the "interesting" process is thus: * * 1) Only foreground processes are eligible - implied. * 2) Runnable processes are favored over anything else. The runner * with the highest cpu utilization is picked (p_estcpu). Ties are * broken by picking the highest pid. * 3) The sleeper with the shortest sleep time is next. With ties, * we pick out just "short-term" sleepers (P_SINTR == 0). * 4) Further ties are broken by picking the highest pid. */ #define ISRUN(p, val) \ do { \ struct thread *td; \ val = 0; \ FOREACH_THREAD_IN_PROC(p, td) { \ if (TD_ON_RUNQ(td) || \ TD_IS_RUNNING(td)) { \ val = 1; \ break; \ } \ } \ } while (0) #define TESTAB(a, b) ((a)<<1 | (b)) #define ONLYA 2 #define ONLYB 1 #define BOTH 3 static int proc_compare(struct proc *p1, struct proc *p2) { int esta, estb; struct ksegrp *kg; mtx_assert(&sched_lock, MA_OWNED); if (p1 == NULL) return (1); ISRUN(p1, esta); ISRUN(p2, estb); /* * see if at least one of them is runnable */ switch (TESTAB(esta, estb)) { case ONLYA: return (0); case ONLYB: return (1); case BOTH: /* * tie - favor one with highest recent cpu utilization */ esta = estb = 0; FOREACH_KSEGRP_IN_PROC(p1,kg) { esta += kg->kg_estcpu; } FOREACH_KSEGRP_IN_PROC(p2,kg) { estb += kg->kg_estcpu; } if (estb > esta) return (1); if (esta > estb) return (0); return (p2->p_pid > p1->p_pid); /* tie - return highest pid */ } /* * weed out zombies */ switch (TESTAB(p1->p_state == PRS_ZOMBIE, p2->p_state == PRS_ZOMBIE)) { case ONLYA: return (1); case ONLYB: return (0); case BOTH: return (p2->p_pid > p1->p_pid); /* tie - return highest pid */ } #if 0 /* XXXKSE */ /* * pick the one with the smallest sleep time */ if (p2->p_slptime > p1->p_slptime) return (0); if (p1->p_slptime > p2->p_slptime) return (1); /* * favor one sleeping in a non-interruptible sleep */ if (p1->p_sflag & PS_SINTR && (p2->p_sflag & PS_SINTR) == 0) return (1); if (p2->p_sflag & PS_SINTR && (p1->p_sflag & PS_SINTR) == 0) return (0); #endif return (p2->p_pid > p1->p_pid); /* tie - return highest pid */ } /* * Output char to tty; console putchar style. */ int tputchar(int c, struct tty *tp) { int s; s = spltty(); if (!ISSET(tp->t_state, TS_CONNECTED)) { splx(s); return (-1); } if (c == '\n') (void)ttyoutput('\r', tp); (void)ttyoutput(c, tp); ttstart(tp); splx(s); return (0); } /* * Sleep on chan, returning ERESTART if tty changed while we napped and * returning any errors (e.g. EINTR/EWOULDBLOCK) reported by tsleep. If * the tty is revoked, restarting a pending call will redo validation done * at the start of the call. */ int ttysleep(struct tty *tp, void *chan, int pri, char *wmesg, int timo) { int error; int gen; gen = tp->t_gen; error = tsleep(chan, pri, wmesg, timo); if (error) return (error); return (tp->t_gen == gen ? 0 : ERESTART); } /* * Allocate a tty struct. Clists in the struct will be allocated by * ttyopen(). */ struct tty * ttymalloc(struct tty *tp) { if (tp) return(tp); tp = malloc(sizeof *tp, M_TTYS, M_WAITOK | M_ZERO); tp->t_timeout = -1; SLIST_INSERT_HEAD(&tty_list, tp, t_list); return (tp); } #if 0 /* XXX not yet usable: session leader holds a ref (see kern_exit.c). */ /* * Free a tty struct. Clists in the struct should have been freed by * ttyclose(). */ void ttyfree(struct tty *tp) { free(tp, M_TTYS); } #endif /* 0 */ static int sysctl_kern_ttys(SYSCTL_HANDLER_ARGS) { struct tty *tp; struct xtty xt; int error; SLIST_FOREACH(tp, &tty_list, t_list) { bzero(&xt, sizeof xt); xt.xt_size = sizeof xt; #define XT_COPY(field) xt.xt_##field = tp->t_##field xt.xt_rawcc = tp->t_rawq.c_cc; xt.xt_cancc = tp->t_canq.c_cc; xt.xt_outcc = tp->t_outq.c_cc; XT_COPY(line); if (tp->t_dev) xt.xt_dev = dev2udev(tp->t_dev); XT_COPY(state); XT_COPY(flags); XT_COPY(timeout); if (tp->t_pgrp) xt.xt_pgid = tp->t_pgrp->pg_id; if (tp->t_session) xt.xt_sid = tp->t_session->s_sid; XT_COPY(termios); XT_COPY(winsize); XT_COPY(column); XT_COPY(rocount); XT_COPY(rocol); XT_COPY(ififosize); XT_COPY(ihiwat); XT_COPY(ilowat); XT_COPY(ispeedwat); XT_COPY(ohiwat); XT_COPY(olowat); XT_COPY(ospeedwat); #undef XT_COPY error = SYSCTL_OUT(req, &xt, sizeof xt); if (error) return (error); } return (0); } SYSCTL_PROC(_kern, OID_AUTO, ttys, CTLTYPE_OPAQUE|CTLFLAG_RD, 0, 0, sysctl_kern_ttys, "S,xtty", "All ttys"); SYSCTL_LONG(_kern, OID_AUTO, tty_nin, CTLFLAG_RD, &tk_nin, 0, "Total TTY in characters"); SYSCTL_LONG(_kern, OID_AUTO, tty_nout, CTLFLAG_RD, &tk_nout, 0, "Total TTY out characters"); void nottystop(struct tty *tp, int rw) { return; } int ttyread(dev_t dev, struct uio *uio, int flag) { struct tty *tp; KASSERT(devsw(dev)->d_flags & D_TTY, ("ttyread() called on non D_TTY device (%s)", devtoname(dev))); tp = dev->si_tty; KASSERT(tp != NULL, ("ttyread(): no tty pointer on device (%s)", devtoname(dev))); if (tp == NULL) return (ENODEV); return (ttyld_read(tp, uio, flag)); } int ttywrite(dev_t dev, struct uio *uio, int flag) { struct tty *tp; KASSERT(devsw(dev)->d_flags & D_TTY, ("ttywrite() called on non D_TTY device (%s)", devtoname(dev))); tp = dev->si_tty; KASSERT(tp != NULL, ("ttywrite(): no tty pointer on device (%s)", devtoname(dev))); if (tp == NULL) return (ENODEV); return (ttyld_write(tp, uio, flag)); } int ttyioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td) { struct tty *tp; int error; tp = dev->si_tty; error = ttyld_ioctl(tp, cmd, data, flag, td); if (error == ENOIOCTL) error = ttioctl(tp, cmd, data, flag); if (error != ENOIOCTL) return (error); return (ENOTTY); } + +int +ttyldoptim(struct tty *tp) +{ + struct termios *t; + + t = &tp->t_termios; + if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) + && (!(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; + return (linesw[tp->t_line].l_hotchar); +} diff --git a/sys/pc98/cbus/sio.c b/sys/pc98/cbus/sio.c index e2c652f1d47f..12c824371a20 100644 --- a/sys/pc98/cbus/sio.c +++ b/sys/pc98/cbus/sio.c @@ -1,5063 +1,5043 @@ /*- * Copyright (c) 1991 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. * 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$ * from: @(#)com.c 7.5 (Berkeley) 5/16/91 * from: i386/isa sio.c,v 1.234 */ #include "opt_comconsole.h" #include "opt_compat.h" #include "opt_ddb.h" #include "opt_sio.h" /* * Serial driver, based on 386BSD-0.1 com driver. * Mostly rewritten to use pseudo-DMA. * Works for National Semiconductor NS8250-NS16550AF UARTs. * COM driver, based on HP dca driver. * * Changes for PC-Card integration: * - Added PC-Card driver table and handlers */ /*=============================================================== * 386BSD(98),FreeBSD-1.1x(98) com driver. * ----- * modified for PC9801 by M.Ishii * Kyoto University Microcomputer Club (KMC) * Chou "TEFUTEFU" Hirotomi * Kyoto Univ. the faculty of medicine *=============================================================== * FreeBSD-2.0.1(98) sio driver. * ----- * modified for pc98 Internal i8251 and MICRO CORE MC16550II * T.Koike(hfc01340@niftyserve.or.jp) * implement kernel device configuration * aizu@orient.center.nitech.ac.jp * * Notes. * ----- * PC98 localization based on 386BSD(98) com driver. Using its PC98 local * functions. * This driver is under debugging,has bugs. */ /* * modified for AIWA B98-01 * by T.Hatanou last update: 15 Sep.1995 */ /* * Modified by Y.Takahashi of Kogakuin University. */ /* * modified for 8251(FIFO) by Seigo TANIMURA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if DDB > 0 #include #endif #include #include #include #include #ifdef PC98 #include #include #endif #ifdef COM_ESP #include #endif #include #ifdef PC98 #include #include #endif #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) #define MINOR_TO_UNIT(mynor) ((((mynor) & ~0xffffU) >> (8 + 3)) \ | ((mynor) & 0x1f)) #define UNIT_TO_MINOR(unit) ((((unit) & ~0x1fU) << (8 + 3)) \ | ((unit) & 0x1f)) /* * Meaning of flags: * * 0x00000001 shared IRQs * 0x00000002 disable FIFO * 0x00000008 recover sooner from lost output interrupts * 0x00000010 device is potential system console * 0x00000020 device is forced to become system console * 0x00000040 device is reserved for low-level IO * 0x00000080 use this port for remote kernel debugging * 0x0000??00 minor number of master port * 0x00010000 PPS timestamping on CTS instead of DCD * 0x00080000 IIR_TXRDY bug * 0x00400000 If no comconsole found then mark as a comconsole * 0x1?000000 interface type */ #ifdef COM_MULTIPORT /* checks in flags for multiport and which is multiport "master chip" * for a given card */ #define COM_ISMULTIPORT(flags) ((flags) & 0x01) #define COM_MPMASTER(flags) (((flags) >> 8) & 0x0ff) #ifndef PC98 #define COM_NOTAST4(flags) ((flags) & 0x04) #endif #else #define COM_ISMULTIPORT(flags) (0) #endif /* COM_MULTIPORT */ #define COM_C_IIR_TXRDYBUG 0x80000 #define COM_CONSOLE(flags) ((flags) & 0x10) #define COM_DEBUGGER(flags) ((flags) & 0x80) #ifndef PC98 #define COM_FIFOSIZE(flags) (((flags) & 0xff000000) >> 24) #endif #define COM_FORCECONSOLE(flags) ((flags) & 0x20) #define COM_IIR_TXRDYBUG(flags) ((flags) & COM_C_IIR_TXRDYBUG) #define COM_LLCONSOLE(flags) ((flags) & 0x40) #define COM_LOSESOUTINTS(flags) ((flags) & 0x08) #define COM_NOFIFO(flags) ((flags) & 0x02) #ifndef PC98 #define COM_NOSCR(flags) ((flags) & 0x100000) #endif #define COM_PPSCTS(flags) ((flags) & 0x10000) #ifndef PC98 #define COM_ST16650A(flags) ((flags) & 0x20000) #define COM_TI16754(flags) ((flags) & 0x200000) #endif #define sio_getreg(com, off) \ (bus_space_read_1((com)->bst, (com)->bsh, (off))) #define sio_setreg(com, off, value) \ (bus_space_write_1((com)->bst, (com)->bsh, (off), (value))) /* * com state bits. * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher * than the other bits so that they can be tested as a group without masking * off the low bits. * * The following com and tty flags correspond closely: * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and * comstop()) * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ #define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ #define CSE_BUSYCHECK 1 /* siobusycheck() scheduled */ static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 "interrupt-level buffer overflow", #define CE_TTY_BUF_OVERFLOW 2 "tty-level buffer overflow", }; #define CE_NTYPES 3 #define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) /* types. XXX - should be elsewhere */ typedef u_int Port_t; /* hardware port */ typedef u_char bool_t; /* boolean */ /* queue of linear buffers */ struct lbq { u_char *l_head; /* next char to process */ u_char *l_tail; /* one past the last char to process */ struct lbq *l_next; /* next in queue */ bool_t l_queued; /* nonzero if queued */ }; /* com device structure */ struct com_s { u_char state; /* miscellaneous flag bits */ bool_t active_out; /* nonzero if the callout device is open */ u_char cfcr_image; /* copy of value written to CFCR */ #ifdef COM_ESP bool_t esp; /* is this unit a hayes esp board? */ #endif u_char extra_state; /* more flag bits, separate for order trick */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t loses_outints; /* nonzero if device loses output interrupts */ u_char mcr_image; /* copy of value written to MCR */ #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ bool_t no_irq; /* nonzero if irq is not attached */ bool_t gone; /* hardware disappeared */ bool_t poll; /* nonzero if polling is required */ bool_t poll_output; /* nonzero if polling for output is required */ bool_t st16650a; /* nonzero if Startech 16650A compatible */ int unit; /* unit number */ int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ u_int flags; /* copy of device flags */ u_int tx_fifo_size; u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly * because there would be too many side effects to handle conveniently. * Instead, it reads copies of the registers stored here by the * interrupt handler. */ u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ibufold; /* old input buffer, to be freed */ u_char *ihighwater; /* threshold in input buffer */ u_char *iptr; /* next free spot in input buffer */ int ibufsize; /* size of ibuf (not include error bytes) */ int ierroff; /* offset of error bytes in ibuf */ struct lbq obufq; /* head of queue of output buffers */ struct lbq obufs[2]; /* output buffers */ bus_space_tag_t bst; bus_space_handle_t bsh; #ifdef PC98 Port_t cmd_port; Port_t sts_port; Port_t in_modem_port; Port_t intr_ctrl_port; Port_t rsabase; /* Iobase address of an I/O-DATA RSA board. */ int intr_enable; int pc98_prev_modem_status; int pc98_modem_delta; int modem_car_chg_timer; int pc98_prev_siocmd; int pc98_prev_siomod; int modem_checking; int pc98_if_type; bool_t pc98_8251fifo; bool_t pc98_8251fifo_enable; #endif /* PC98 */ Port_t data_port; /* i/o ports */ #ifdef COM_ESP Port_t esp_port; #endif Port_t int_ctl_port; Port_t int_id_port; Port_t modem_ctl_port; Port_t line_status_port; Port_t modem_status_port; struct tty *tp; /* cross reference */ /* Initial state. */ struct termios it_in; /* should be in struct tty */ struct termios it_out; /* Lock state. */ struct termios lt_in; /* should be in struct tty */ struct termios lt_out; bool_t do_timestamp; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_timestamp; struct pps_state pps; int pps_bit; #ifdef ALT_BREAK_TO_DEBUGGER int alt_brk_state; #endif u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; u_long error_counts[CE_NTYPES]; u_long rclk; struct resource *irqres; struct resource *ioportres; int ioportrid; void *cookie; dev_t devs[6]; /* * Data area for output buffers. Someday we should build the output * buffer queue without copying data. */ #ifdef PC98 int obufsize; u_char *obuf1; u_char *obuf2; #else u_char obuf1[256]; u_char obuf2[256]; #endif }; #ifdef COM_ESP static int espattach(struct com_s *com, Port_t esp_port); #endif static timeout_t siobusycheck; static u_int siodivisor(u_long rclk, speed_t speed); static timeout_t siodtrwakeup; static void comhardclose(struct com_s *com); static void sioinput(struct com_s *com); static void siointr1(struct com_s *com); static void siointr(void *arg); static int commctl(struct com_s *com, int bits, int how); static int comparam(struct tty *tp, struct termios *t); static void siopoll(void *); static void siosettimeout(void); static int siosetwater(struct com_s *com, speed_t speed); static void comstart(struct tty *tp); static void comstop(struct tty *tp, int rw); static timeout_t comwakeup; -static void disc_optim(struct tty *tp, struct termios *t, - struct com_s *com); char sio_driver_name[] = "sio"; static struct mtx sio_lock; static int sio_inited; /* table and macro for fast conversion from a unit number to its com struct */ devclass_t sio_devclass; #define com_addr(unit) ((struct com_s *) \ devclass_get_softc(sio_devclass, unit)) /* XXX */ static d_open_t sioopen; static d_close_t sioclose; static d_read_t sioread; static d_write_t siowrite; static d_ioctl_t sioioctl; static struct cdevsw sio_cdevsw = { .d_version = D_VERSION, .d_open = sioopen, .d_close = sioclose, .d_read = sioread, .d_write = siowrite, .d_ioctl = sioioctl, .d_name = sio_driver_name, .d_flags = D_TTY | D_NEEDGIANT, }; int comconsole = -1; static volatile speed_t comdefaultrate = CONSPEED; static u_long comdefaultrclk = DEFAULT_RCLK; SYSCTL_ULONG(_machdep, OID_AUTO, conrclk, CTLFLAG_RW, &comdefaultrclk, 0, ""); static speed_t gdbdefaultrate = GDBSPEED; SYSCTL_UINT(_machdep, OID_AUTO, gdbspeed, CTLFLAG_RW, &gdbdefaultrate, GDBSPEED, ""); static u_int com_events; /* input chars + weighted output completions */ static Port_t siocniobase; static int siocnunit = -1; static Port_t siogdbiobase; static int siogdbunit = -1; static void *sio_slow_ih; static void *sio_fast_ih; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); static int sio_numunits; #ifdef PC98 struct siodev { short if_type; short irq; Port_t cmd, sts, ctrl, mod; }; static int sysclock; #define COM_INT_DISABLE {int previpri; previpri=spltty(); #define COM_INT_ENABLE splx(previpri);} #define IEN_TxFLAG IEN_Tx #define COM_CARRIER_DETECT_EMULATE 0 #define PC98_CHECK_MODEM_INTERVAL (hz/10) #define DCD_OFF_TOLERANCE 2 #define DCD_ON_RECOGNITION 2 #define IS_8251(if_type) (!(if_type & 0x10)) #define COM1_EXT_CLOCK 0x40000 static void commint(dev_t dev); static void com_tiocm_set(struct com_s *com, int msr); static void com_tiocm_bis(struct com_s *com, int msr); static void com_tiocm_bic(struct com_s *com, int msr); static int com_tiocm_get(struct com_s *com); static int com_tiocm_get_delta(struct com_s *com); static void pc98_msrint_start(dev_t dev); static void com_cflag_and_speed_set(struct com_s *com, int cflag, int speed); static int pc98_ttspeedtab(struct com_s *com, int speed, u_int *divisor); static int pc98_get_modem_status(struct com_s *com); static timeout_t pc98_check_msr; static void pc98_set_baud_rate(struct com_s *com, u_int count); static void pc98_i8251_reset(struct com_s *com, int mode, int command); static void pc98_disable_i8251_interrupt(struct com_s *com, int mod); static void pc98_enable_i8251_interrupt(struct com_s *com, int mod); static int pc98_check_i8251_interrupt(struct com_s *com); static int pc98_i8251_get_cmd(struct com_s *com); static int pc98_i8251_get_mod(struct com_s *com); static void pc98_i8251_set_cmd(struct com_s *com, int x); static void pc98_i8251_or_cmd(struct com_s *com, int x); static void pc98_i8251_clear_cmd(struct com_s *com, int x); static void pc98_i8251_clear_or_cmd(struct com_s *com, int clr, int x); static int pc98_check_if_type(device_t dev, struct siodev *iod); static int pc98_check_8251vfast(void); static int pc98_check_8251fifo(void); static void pc98_check_sysclock(void); static void pc98_set_ioport(struct com_s *com); #define com_int_Tx_disable(com) \ pc98_disable_i8251_interrupt(com,IEN_Tx|IEN_TxEMP) #define com_int_Tx_enable(com) \ pc98_enable_i8251_interrupt(com,IEN_TxFLAG) #define com_int_Rx_disable(com) \ pc98_disable_i8251_interrupt(com,IEN_Rx) #define com_int_Rx_enable(com) \ pc98_enable_i8251_interrupt(com,IEN_Rx) #define com_int_TxRx_disable(com) \ pc98_disable_i8251_interrupt(com,IEN_Tx|IEN_TxEMP|IEN_Rx) #define com_int_TxRx_enable(com) \ pc98_enable_i8251_interrupt(com,IEN_TxFLAG|IEN_Rx) #define com_send_break_on(com) \ pc98_i8251_or_cmd(com,CMD8251_SBRK) #define com_send_break_off(com) \ pc98_i8251_clear_cmd(com,CMD8251_SBRK) static struct speedtab pc98speedtab[] = { /* internal RS232C interface */ { 0, 0, }, { 50, 50, }, { 75, 75, }, { 150, 150, }, { 200, 200, }, { 300, 300, }, { 600, 600, }, { 1200, 1200, }, { 2400, 2400, }, { 4800, 4800, }, { 9600, 9600, }, { 19200, 19200, }, { 38400, 38400, }, { 51200, 51200, }, { 76800, 76800, }, { 20800, 20800, }, { 31200, 31200, }, { 41600, 41600, }, { 62400, 62400, }, { -1, -1 } }; static struct speedtab pc98fast_speedtab[] = { { 9600, 0x80 | (DEFAULT_RCLK / (16 * (9600))), }, { 19200, 0x80 | (DEFAULT_RCLK / (16 * (19200))), }, { 38400, 0x80 | (DEFAULT_RCLK / (16 * (38400))), }, { 57600, 0x80 | (DEFAULT_RCLK / (16 * (57600))), }, { 115200, 0x80 | (DEFAULT_RCLK / (16 * (115200))), }, { -1, -1 } }; static struct speedtab comspeedtab_pio9032b[] = { { 300, 6, }, { 600, 5, }, { 1200, 4, }, { 2400, 3, }, { 4800, 2, }, { 9600, 1, }, { 19200, 0, }, { 38400, 7, }, { -1, -1 } }; static struct speedtab comspeedtab_b98_01[] = { { 75, 11, }, { 150, 10, }, { 300, 9, }, { 600, 8, }, { 1200, 7, }, { 2400, 6, }, { 4800, 5, }, { 9600, 4, }, { 19200, 3, }, { 38400, 2, }, { 76800, 1, }, { 153600, 0, }, { -1, -1 } }; static struct speedtab comspeedtab_ind[] = { { 300, 1536, }, { 600, 768, }, { 1200, 384, }, { 2400, 192, }, { 4800, 96, }, { 9600, 48, }, { 19200, 24, }, { 38400, 12, }, { 57600, 8, }, { 115200, 4, }, { 153600, 3, }, { 230400, 2, }, { 460800, 1, }, { -1, -1 } }; struct { char *name; short port_table[7]; short irr_mask; struct speedtab *speedtab; short check_irq; } if_8251_type[] = { /* COM_IF_INTERNAL */ { " (internal)", {0x30, 0x32, 0x32, 0x33, 0x35, -1, -1}, -1, pc98speedtab, 1 }, /* COM_IF_PC9861K_1 */ { " (PC9861K)", {0xb1, 0xb3, 0xb3, 0xb0, 0xb0, -1, -1}, 3, NULL, 1 }, /* COM_IF_PC9861K_2 */ { " (PC9861K)", {0xb9, 0xbb, 0xbb, 0xb2, 0xb2, -1, -1}, 3, NULL, 1 }, /* COM_IF_IND_SS_1 */ { " (IND-SS)", {0xb1, 0xb3, 0xb3, 0xb0, 0xb0, 0xb3, -1}, 3, comspeedtab_ind, 1 }, /* COM_IF_IND_SS_2 */ { " (IND-SS)", {0xb9, 0xbb, 0xbb, 0xb2, 0xb2, 0xbb, -1}, 3, comspeedtab_ind, 1 }, /* COM_IF_PIO9032B_1 */ { " (PIO9032B)", {0xb1, 0xb3, 0xb3, 0xb0, 0xb0, 0xb8, -1}, 7, comspeedtab_pio9032b, 1 }, /* COM_IF_PIO9032B_2 */ { " (PIO9032B)", {0xb9, 0xbb, 0xbb, 0xb2, 0xb2, 0xba, -1}, 7, comspeedtab_pio9032b, 1 }, /* COM_IF_B98_01_1 */ { " (B98-01)", {0xb1, 0xb3, 0xb3, 0xb0, 0xb0, 0xd1, 0xd3}, 7, comspeedtab_b98_01, 0 }, /* COM_IF_B98_01_2 */ { " (B98-01)", {0xb9, 0xbb, 0xbb, 0xb2, 0xb2, 0xd5, 0xd7}, 7, comspeedtab_b98_01, 0 }, }; #define PC98SIO_data_port(type) (if_8251_type[type].port_table[0]) #define PC98SIO_cmd_port(type) (if_8251_type[type].port_table[1]) #define PC98SIO_sts_port(type) (if_8251_type[type].port_table[2]) #define PC98SIO_in_modem_port(type) (if_8251_type[type].port_table[3]) #define PC98SIO_intr_ctrl_port(type) (if_8251_type[type].port_table[4]) #define PC98SIO_baud_rate_port(type) (if_8251_type[type].port_table[5]) #define PC98SIO_func_port(type) (if_8251_type[type].port_table[6]) #define I8251F_data 0x130 #define I8251F_lsr 0x132 #define I8251F_msr 0x134 #define I8251F_iir 0x136 #define I8251F_fcr 0x138 #define I8251F_div 0x13a static bus_addr_t port_table_0[] = {0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007}; static bus_addr_t port_table_1[] = {0x000, 0x002, 0x004, 0x006, 0x008, 0x00a, 0x00c, 0x00e}; static bus_addr_t port_table_8[] = {0x000, 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700}; static bus_addr_t port_table_rsa[] = { 0x008, 0x009, 0x00a, 0x00b, 0x00c, 0x00d, 0x00e, 0x00f, 0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007 }; struct { char *name; short irr_read; short irr_write; bus_addr_t *iat; bus_size_t iatsz; u_long rclk; } if_16550a_type[] = { /* COM_IF_RSA98 */ {" (RSA-98)", -1, -1, port_table_0, IO_COMSIZE, DEFAULT_RCLK}, /* COM_IF_NS16550 */ {"", -1, -1, port_table_0, IO_COMSIZE, DEFAULT_RCLK}, /* COM_IF_SECOND_CCU */ {"", -1, -1, port_table_0, IO_COMSIZE, DEFAULT_RCLK}, /* COM_IF_MC16550II */ {" (MC16550II)", -1, 0x1000, port_table_8, IO_COMSIZE, DEFAULT_RCLK * 4}, /* COM_IF_MCRS98 */ {" (MC-RS98)", -1, 0x1000, port_table_8, IO_COMSIZE, DEFAULT_RCLK * 4}, /* COM_IF_RSB3000 */ {" (RSB-3000)", 0xbf, -1, port_table_1, IO_COMSIZE, DEFAULT_RCLK * 10}, /* COM_IF_RSB384 */ {" (RSB-384)", 0xbf, -1, port_table_1, IO_COMSIZE, DEFAULT_RCLK * 10}, /* COM_IF_MODEM_CARD */ {"", -1, -1, port_table_0, IO_COMSIZE, DEFAULT_RCLK}, /* COM_IF_RSA98III */ {" (RSA-98III)", -1, -1, port_table_rsa, 16, DEFAULT_RCLK * 8}, /* COM_IF_ESP98 */ {" (ESP98)", -1, -1, port_table_1, IO_COMSIZE, DEFAULT_RCLK * 4}, }; #endif /* PC98 */ #ifdef COM_ESP #ifdef PC98 /* XXX configure this properly. */ /* XXX quite broken for new-bus. */ static Port_t likely_com_ports[] = { 0, 0xb0, 0xb1, 0 }; static Port_t likely_esp_ports[] = { 0xc0d0, 0 }; #define ESP98_CMD1 (ESP_CMD1 * 0x100) #define ESP98_CMD2 (ESP_CMD2 * 0x100) #define ESP98_STATUS1 (ESP_STATUS1 * 0x100) #define ESP98_STATUS2 (ESP_STATUS2 * 0x100) #else /* PC98 */ /* XXX configure this properly. */ static Port_t likely_com_ports[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, }; static Port_t likely_esp_ports[] = { 0x140, 0x180, 0x280, 0 }; #endif /* PC98 */ #endif /* * handle sysctl read/write requests for console speed * * In addition to setting comdefaultrate for I/O through /dev/console, * also set the initial and lock values for the /dev/ttyXX device * if there is one associated with the console. Finally, if the /dev/tty * device has already been open, change the speed on the open running port * itself. */ static int sysctl_machdep_comdefaultrate(SYSCTL_HANDLER_ARGS) { int error, s; speed_t newspeed; struct com_s *com; struct tty *tp; newspeed = comdefaultrate; error = sysctl_handle_opaque(oidp, &newspeed, sizeof newspeed, req); if (error || !req->newptr) return (error); comdefaultrate = newspeed; if (comconsole < 0) /* serial console not selected? */ return (0); com = com_addr(comconsole); if (com == NULL) return (ENXIO); /* * set the initial and lock rates for /dev/ttydXX and /dev/cuaXX * (note, the lock rates really are boolean -- if non-zero, disallow * speed changes) */ com->it_in.c_ispeed = com->it_in.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_out.c_ispeed = com->it_out.c_ospeed = com->lt_out.c_ispeed = com->lt_out.c_ospeed = comdefaultrate; /* * if we're open, change the running rate too */ tp = com->tp; if (tp && (tp->t_state & TS_ISOPEN)) { tp->t_termios.c_ispeed = tp->t_termios.c_ospeed = comdefaultrate; s = spltty(); error = comparam(tp, &tp->t_termios); splx(s); } return error; } SYSCTL_PROC(_machdep, OID_AUTO, conspeed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_machdep_comdefaultrate, "I", ""); /* * Unload the driver and clear the table. * XXX this is mostly wrong. * XXX TODO: * This is usually called when the card is ejected, but * can be caused by a kldunload of a controller driver. * The idea is to reset the driver's view of the device * and ensure that any driver entry points such as * read and write do not hang. */ int siodetach(dev) device_t dev; { struct com_s *com; int i; com = (struct com_s *) device_get_softc(dev); if (com == NULL) { device_printf(dev, "NULL com in siounload\n"); return (0); } com->gone = TRUE; for (i = 0 ; i < 6; i++) destroy_dev(com->devs[i]); if (com->irqres) { bus_teardown_intr(dev, com->irqres, com->cookie); bus_release_resource(dev, SYS_RES_IRQ, 0, com->irqres); } if (com->ioportres) bus_release_resource(dev, SYS_RES_IOPORT, com->ioportrid, com->ioportres); if (com->tp && (com->tp->t_state & TS_ISOPEN)) { device_printf(dev, "still open, forcing close\n"); ttyld_close(com->tp, 0); ttyclose(com->tp); } else { if (com->ibuf != NULL) free(com->ibuf, M_DEVBUF); #ifdef PC98 if (com->obuf1 != NULL) free(com->obuf1, M_DEVBUF); #endif device_set_softc(dev, NULL); free(com, M_DEVBUF); } return (0); } int sioprobe(dev, xrid, rclk, noprobe) device_t dev; int xrid; u_long rclk; int noprobe; { #if 0 static bool_t already_init; device_t xdev; #endif struct com_s *com; u_int divisor; bool_t failures[10]; int fn; device_t idev; Port_t iobase; intrmask_t irqmap[4]; intrmask_t irqs; u_char mcr_image; int result; u_long xirq; u_int flags = device_get_flags(dev); int rid; struct resource *port; #ifdef PC98 int tmp; struct siodev iod; #endif #ifdef PC98 iod.if_type = GET_IFTYPE(flags); if ((iod.if_type < 0 || iod.if_type > COM_IF_END1) && (iod.if_type < 0x10 || iod.if_type > COM_IF_END2)) return ENXIO; #endif rid = xrid; #ifdef PC98 if (IS_8251(iod.if_type)) { port = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); } else if (iod.if_type == COM_IF_MODEM_CARD || iod.if_type == COM_IF_RSA98III || isa_get_vendorid(dev)) { port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, if_16550a_type[iod.if_type & 0x0f].iatsz, RF_ACTIVE); } else { port = isa_alloc_resourcev(dev, SYS_RES_IOPORT, &rid, if_16550a_type[iod.if_type & 0x0f].iat, if_16550a_type[iod.if_type & 0x0f].iatsz, RF_ACTIVE); } #else port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, IO_COMSIZE, RF_ACTIVE); #endif if (!port) return (ENXIO); #ifdef PC98 if (!IS_8251(iod.if_type)) { if (isa_load_resourcev(port, if_16550a_type[iod.if_type & 0x0f].iat, if_16550a_type[iod.if_type & 0x0f].iatsz) != 0) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return ENXIO; } } #endif com = malloc(sizeof(*com), M_DEVBUF, M_NOWAIT | M_ZERO); if (com == NULL) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return (ENOMEM); } device_set_softc(dev, com); com->bst = rman_get_bustag(port); com->bsh = rman_get_bushandle(port); #ifdef PC98 if (!IS_8251(iod.if_type) && rclk == 0) rclk = if_16550a_type[iod.if_type & 0x0f].rclk; #else if (rclk == 0) rclk = DEFAULT_RCLK; #endif com->rclk = rclk; while (sio_inited != 2) if (atomic_cmpset_int(&sio_inited, 0, 1)) { mtx_init(&sio_lock, sio_driver_name, NULL, (comconsole != -1) ? MTX_SPIN | MTX_QUIET : MTX_SPIN); atomic_store_rel_int(&sio_inited, 2); } #if 0 /* * XXX this is broken - when we are first called, there are no * previously configured IO ports. We could hard code * 0x3f8, 0x2f8, 0x3e8, 0x2e8 etc but that's probably worse. * This code has been doing nothing since the conversion since * "count" is zero the first time around. */ if (!already_init) { /* * Turn off MCR_IENABLE for all likely serial ports. An unused * port with its MCR_IENABLE gate open will inhibit interrupts * from any used port that shares the interrupt vector. * XXX the gate enable is elsewhere for some multiports. */ device_t *devs; int count, i, xioport; #ifdef PC98 int xiftype; #endif devclass_get_devices(sio_devclass, &devs, &count); #ifdef PC98 for (i = 0; i < count; i++) { xdev = devs[i]; xioport = bus_get_resource_start(xdev, SYS_RES_IOPORT, 0); xiftype = GET_IFTYPE(device_get_flags(xdev)); if (device_is_enabled(xdev) && xioport > 0) { if (IS_8251(xiftype)) outb((xioport & 0xff00) | PC98SIO_cmd_port(xiftype & 0x0f), 0xf2); else outb(xioport + if_16550a_type[xiftype & 0x0f].iat[com_mcr], 0); } } #else for (i = 0; i < count; i++) { xdev = devs[i]; if (device_is_enabled(xdev) && bus_get_resource(xdev, SYS_RES_IOPORT, 0, &xioport, NULL) == 0) outb(xioport + com_mcr, 0); } #endif free(devs, M_TEMP); already_init = TRUE; } #endif if (COM_LLCONSOLE(flags)) { printf("sio%d: reserved for low-level i/o\n", device_get_unit(dev)); bus_release_resource(dev, SYS_RES_IOPORT, rid, port); device_set_softc(dev, NULL); free(com, M_DEVBUF); return (ENXIO); } #ifdef PC98 DELAY(10); /* * If the port is i8251 UART (internal, B98_01) */ if (pc98_check_if_type(dev, &iod) == -1) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); device_set_softc(dev, NULL); free(com, M_DEVBUF); return (ENXIO); } if (iod.irq > 0) bus_set_resource(dev, SYS_RES_IRQ, 0, iod.irq, 1); if (IS_8251(iod.if_type)) { outb(iod.cmd, 0); DELAY(10); outb(iod.cmd, 0); DELAY(10); outb(iod.cmd, 0); DELAY(10); outb(iod.cmd, CMD8251_RESET); DELAY(1000); /* for a while...*/ outb(iod.cmd, 0xf2); /* MODE (dummy) */ DELAY(10); outb(iod.cmd, 0x01); /* CMD (dummy) */ DELAY(1000); /* for a while...*/ if (( inb(iod.sts) & STS8251_TxEMP ) == 0 ) { result = (ENXIO); } if (if_8251_type[iod.if_type & 0x0f].check_irq) { COM_INT_DISABLE tmp = ( inb( iod.ctrl ) & ~(IEN_Rx|IEN_TxEMP|IEN_Tx)); outb( iod.ctrl, tmp|IEN_TxEMP ); DELAY(10); result = isa_irq_pending() ? 0 : ENXIO; outb( iod.ctrl, tmp ); COM_INT_ENABLE } else { /* * B98_01 doesn't activate TxEMP interrupt line * when being reset, so we can't check irq pending. */ result = 0; } if (epson_machine_id==0x20) { /* XXX */ result = 0; } bus_release_resource(dev, SYS_RES_IOPORT, rid, port); if (result) { device_set_softc(dev, NULL); free(com, M_DEVBUF); } return result; } #endif /* PC98 */ /* * If the device is on a multiport card and has an AST/4 * compatible interrupt control register, initialize this * register and prepare to leave MCR_IENABLE clear in the mcr. * Otherwise, prepare to set MCR_IENABLE in the mcr. * Point idev to the device struct giving the correct id_irq. * This is the struct for the master device if there is one. */ idev = dev; mcr_image = MCR_IENABLE; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(flags)) { #ifndef PC98 Port_t xiobase; u_long io; #endif idev = devclass_get_device(sio_devclass, COM_MPMASTER(flags)); if (idev == NULL) { printf("sio%d: master device %d not configured\n", device_get_unit(dev), COM_MPMASTER(flags)); idev = dev; } #ifndef PC98 if (!COM_NOTAST4(flags)) { if (bus_get_resource(idev, SYS_RES_IOPORT, 0, &io, NULL) == 0) { xiobase = io; if (bus_get_resource(idev, SYS_RES_IRQ, 0, NULL, NULL) == 0) outb(xiobase + com_scr, 0x80); else outb(xiobase + com_scr, 0); } mcr_image = 0; } #endif } #endif /* COM_MULTIPORT */ if (bus_get_resource(idev, SYS_RES_IRQ, 0, NULL, NULL) != 0) mcr_image = 0; bzero(failures, sizeof failures); iobase = rman_get_start(port); #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) { mcr_image = 0; outb(iobase + rsa_msr, 0x04); outb(iobase + rsa_frr, 0x00); if ((inb(iobase + rsa_srr) & 0x36) != 0x36) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); device_set_softc(dev, NULL); free(com, M_DEVBUF); return (ENXIO); } outb(iobase + rsa_ier, 0x00); outb(iobase + rsa_frr, 0x00); outb(iobase + rsa_tivsr, 0x00); outb(iobase + rsa_tcr, 0x00); } tmp = if_16550a_type[iod.if_type & 0x0f].irr_write; if (tmp != -1) { /* MC16550II */ int irqout; switch (isa_get_irq(idev)) { case 3: irqout = 4; break; case 5: irqout = 5; break; case 6: irqout = 6; break; case 12: irqout = 7; break; default: printf("sio%d: irq configuration error\n", device_get_unit(dev)); bus_release_resource(dev, SYS_RES_IOPORT, rid, port); device_set_softc(dev, NULL); free(com, M_DEVBUF); return (ENXIO); } outb((iobase & 0x00ff) | tmp, irqout); } #endif /* * We don't want to get actual interrupts, just masked ones. * Interrupts from this line should already be masked in the ICU, * but mask them in the processor as well in case there are some * (misconfigured) shared interrupts. */ mtx_lock_spin(&sio_lock); /* EXTRA DELAY? */ /* * Initialize the speed and the word size and wait long enough to * drain the maximum of 16 bytes of junk in device output queues. * The speed is undefined after a master reset and must be set * before relying on anything related to output. There may be * junk after a (very fast) soft reboot and (apparently) after * master reset. * XXX what about the UART bug avoided by waiting in comparam()? * We don't want to to wait long enough to drain at 2 bps. */ if (iobase == siocniobase) DELAY((16 + 1) * 1000000 / (comdefaultrate / 10)); else { sio_setreg(com, com_cfcr, CFCR_DLAB | CFCR_8BITS); divisor = siodivisor(rclk, SIO_TEST_SPEED); sio_setreg(com, com_dlbl, divisor & 0xff); sio_setreg(com, com_dlbh, divisor >> 8); sio_setreg(com, com_cfcr, CFCR_8BITS); DELAY((16 + 1) * 1000000 / (SIO_TEST_SPEED / 10)); } /* * Enable the interrupt gate and disable device interupts. This * should leave the device driving the interrupt line low and * guarantee an edge trigger if an interrupt can be generated. */ /* EXTRA DELAY? */ sio_setreg(com, com_mcr, mcr_image); sio_setreg(com, com_ier, 0); DELAY(1000); /* XXX */ irqmap[0] = isa_irq_pending(); /* * Attempt to set loopback mode so that we can send a null byte * without annoying any external device. */ /* EXTRA DELAY? */ sio_setreg(com, com_mcr, mcr_image | MCR_LOOPBACK); /* * Attempt to generate an output interrupt. On 8250's, setting * IER_ETXRDY generates an interrupt independent of the current * setting and independent of whether the THR is empty. On 16450's, * setting IER_ETXRDY generates an interrupt independent of the * current setting. On 16550A's, setting IER_ETXRDY only * generates an interrupt when IER_ETXRDY is not already set. */ sio_setreg(com, com_ier, IER_ETXRDY); #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) outb(iobase + rsa_ier, 0x04); #endif /* * On some 16x50 incompatibles, setting IER_ETXRDY doesn't generate * an interrupt. They'd better generate one for actually doing * output. Loopback may be broken on the same incompatibles but * it's unlikely to do more than allow the null byte out. */ sio_setreg(com, com_data, 0); if (iobase == siocniobase) DELAY((1 + 2) * 1000000 / (comdefaultrate / 10)); else DELAY((1 + 2) * 1000000 / (SIO_TEST_SPEED / 10)); /* * Turn off loopback mode so that the interrupt gate works again * (MCR_IENABLE was hidden). This should leave the device driving * an interrupt line high. It doesn't matter if the interrupt * line oscillates while we are not looking at it, since interrupts * are disabled. */ /* EXTRA DELAY? */ sio_setreg(com, com_mcr, mcr_image); /* * It seems my Xircom CBEM56G Cardbus modem wants to be reset * to 8 bits *again*, or else probe test 0 will fail. * gwk@sgi.com, 4/19/2001 */ sio_setreg(com, com_cfcr, CFCR_8BITS); /* * Some PCMCIA cards (Palido 321s, DC-1S, ...) have the "TXRDY bug", * so we probe for a buggy IIR_TXRDY implementation even in the * noprobe case. We don't probe for it in the !noprobe case because * noprobe is always set for PCMCIA cards and the problem is not * known to affect any other cards. */ if (noprobe) { /* Read IIR a few times. */ for (fn = 0; fn < 2; fn ++) { DELAY(10000); failures[6] = sio_getreg(com, com_iir); } /* IIR_TXRDY should be clear. Is it? */ result = 0; if (failures[6] & IIR_TXRDY) { /* * No. We seem to have the bug. Does our fix for * it work? */ sio_setreg(com, com_ier, 0); if (sio_getreg(com, com_iir) & IIR_NOPEND) { /* Yes. We discovered the TXRDY bug! */ SET_FLAG(dev, COM_C_IIR_TXRDYBUG); } else { /* No. Just fail. XXX */ result = ENXIO; sio_setreg(com, com_mcr, 0); } } else { /* Yes. No bug. */ CLR_FLAG(dev, COM_C_IIR_TXRDYBUG); } sio_setreg(com, com_ier, 0); sio_setreg(com, com_cfcr, CFCR_8BITS); mtx_unlock_spin(&sio_lock); bus_release_resource(dev, SYS_RES_IOPORT, rid, port); if (iobase == siocniobase) result = 0; if (result != 0) { device_set_softc(dev, NULL); free(com, M_DEVBUF); } return (result); } /* * Check that * o the CFCR, IER and MCR in UART hold the values written to them * (the values happen to be all distinct - this is good for * avoiding false positive tests from bus echoes). * o an output interrupt is generated and its vector is correct. * o the interrupt goes away when the IIR in the UART is read. */ /* EXTRA DELAY? */ failures[0] = sio_getreg(com, com_cfcr) - CFCR_8BITS; failures[1] = sio_getreg(com, com_ier) - IER_ETXRDY; failures[2] = sio_getreg(com, com_mcr) - mcr_image; DELAY(10000); /* Some internal modems need this time */ irqmap[1] = isa_irq_pending(); failures[4] = (sio_getreg(com, com_iir) & IIR_IMASK) - IIR_TXRDY; #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) inb(iobase + rsa_srr); #endif DELAY(1000); /* XXX */ irqmap[2] = isa_irq_pending(); failures[6] = (sio_getreg(com, com_iir) & IIR_IMASK) - IIR_NOPEND; #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) inb(iobase + rsa_srr); #endif /* * Turn off all device interrupts and check that they go off properly. * Leave MCR_IENABLE alone. For ports without a master port, it gates * the OUT2 output of the UART to * the ICU input. Closing the gate would give a floating ICU input * (unless there is another device driving it) and spurious interrupts. * (On the system that this was first tested on, the input floats high * and gives a (masked) interrupt as soon as the gate is closed.) */ sio_setreg(com, com_ier, 0); sio_setreg(com, com_cfcr, CFCR_8BITS); /* dummy to avoid bus echo */ failures[7] = sio_getreg(com, com_ier); #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) outb(iobase + rsa_ier, 0x00); #endif DELAY(1000); /* XXX */ irqmap[3] = isa_irq_pending(); failures[9] = (sio_getreg(com, com_iir) & IIR_IMASK) - IIR_NOPEND; #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) { inb(iobase + rsa_srr); outb(iobase + rsa_frr, 0x00); } #endif mtx_unlock_spin(&sio_lock); irqs = irqmap[1] & ~irqmap[0]; if (bus_get_resource(idev, SYS_RES_IRQ, 0, &xirq, NULL) == 0 && ((1 << xirq) & irqs) == 0) { printf( "sio%d: configured irq %ld not in bitmap of probed irqs %#x\n", device_get_unit(dev), xirq, irqs); printf( "sio%d: port may not be enabled\n", device_get_unit(dev)); } if (bootverbose) printf("sio%d: irq maps: %#x %#x %#x %#x\n", device_get_unit(dev), irqmap[0], irqmap[1], irqmap[2], irqmap[3]); result = 0; for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) { sio_setreg(com, com_mcr, 0); result = ENXIO; if (bootverbose) { printf("sio%d: probe failed test(s):", device_get_unit(dev)); for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) printf(" %d", fn); printf("\n"); } break; } bus_release_resource(dev, SYS_RES_IOPORT, rid, port); if (iobase == siocniobase) result = 0; if (result != 0) { device_set_softc(dev, NULL); free(com, M_DEVBUF); } return (result); } #ifdef COM_ESP static int espattach(com, esp_port) struct com_s *com; Port_t esp_port; { u_char dips; u_char val; /* * Check the ESP-specific I/O port to see if we're an ESP * card. If not, return failure immediately. */ if ((inb(esp_port) & 0xf3) == 0) { printf(" port 0x%x is not an ESP board?\n", esp_port); return (0); } /* * We've got something that claims to be a Hayes ESP card. * Let's hope so. */ /* Get the dip-switch configuration */ #ifdef PC98 outb(esp_port + ESP98_CMD1, ESP_GETDIPS); dips = inb(esp_port + ESP98_STATUS1); #else outb(esp_port + ESP_CMD1, ESP_GETDIPS); dips = inb(esp_port + ESP_STATUS1); #endif /* * Bits 0,1 of dips say which COM port we are. */ #ifdef PC98 if ((rman_get_start(com->ioportres) & 0xff) == likely_com_ports[dips & 0x03]) #else if (rman_get_start(com->ioportres) == likely_com_ports[dips & 0x03]) #endif printf(" : ESP"); else { printf(" esp_port has com %d\n", dips & 0x03); return (0); } /* * Check for ESP version 2.0 or later: bits 4,5,6 = 010. */ #ifdef PC98 outb(esp_port + ESP98_CMD1, ESP_GETTEST); val = inb(esp_port + ESP98_STATUS1); /* clear reg 1 */ val = inb(esp_port + ESP98_STATUS2); #else outb(esp_port + ESP_CMD1, ESP_GETTEST); val = inb(esp_port + ESP_STATUS1); /* clear reg 1 */ val = inb(esp_port + ESP_STATUS2); #endif if ((val & 0x70) < 0x20) { printf("-old (%o)", val & 0x70); return (0); } /* * Check for ability to emulate 16550: bit 7 == 1 */ if ((dips & 0x80) == 0) { printf(" slave"); return (0); } /* * Okay, we seem to be a Hayes ESP card. Whee. */ com->esp = TRUE; com->esp_port = esp_port; return (1); } #endif /* COM_ESP */ int sioattach(dev, xrid, rclk) device_t dev; int xrid; u_long rclk; { struct com_s *com; #ifdef COM_ESP Port_t *espp; #endif Port_t iobase; int minorbase; int unit; u_int flags; int rid; struct resource *port; int ret; #ifdef PC98 u_char *obuf; u_long obufsize; int if_type = GET_IFTYPE(device_get_flags(dev)); #endif rid = xrid; #ifdef PC98 if (IS_8251(if_type)) { port = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); } else if (if_type == COM_IF_MODEM_CARD || if_type == COM_IF_RSA98III || isa_get_vendorid(dev)) { port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, if_16550a_type[if_type & 0x0f].iatsz, RF_ACTIVE); } else { port = isa_alloc_resourcev(dev, SYS_RES_IOPORT, &rid, if_16550a_type[if_type & 0x0f].iat, if_16550a_type[if_type & 0x0f].iatsz, RF_ACTIVE); } #else port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, IO_COMSIZE, RF_ACTIVE); #endif if (!port) return (ENXIO); #ifdef PC98 if (!IS_8251(if_type)) { if (isa_load_resourcev(port, if_16550a_type[if_type & 0x0f].iat, if_16550a_type[if_type & 0x0f].iatsz) != 0) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return ENXIO; } } #endif iobase = rman_get_start(port); unit = device_get_unit(dev); com = device_get_softc(dev); flags = device_get_flags(dev); if (unit >= sio_numunits) sio_numunits = unit + 1; #ifdef PC98 obufsize = 256; if (if_type == COM_IF_RSA98III) obufsize = 2048; if ((obuf = malloc(obufsize * 2, M_DEVBUF, M_NOWAIT)) == NULL) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return ENXIO; } bzero(obuf, obufsize * 2); #endif /* * sioprobe() has initialized the device registers as follows: * o cfcr = CFCR_8BITS. * It is most important that CFCR_DLAB is off, so that the * data port is not hidden when we enable interrupts. * o ier = 0. * Interrupts are only enabled when the line is open. * o mcr = MCR_IENABLE, or 0 if the port has AST/4 compatible * interrupt control register or the config specifies no irq. * Keeping MCR_DTR and MCR_RTS off might stop the external * device from sending before we are ready. */ bzero(com, sizeof *com); com->unit = unit; com->ioportres = port; com->ioportrid = rid; com->bst = rman_get_bustag(port); com->bsh = rman_get_bushandle(port); com->cfcr_image = CFCR_8BITS; com->dtr_wait = 3 * hz; com->loses_outints = COM_LOSESOUTINTS(flags) != 0; com->no_irq = bus_get_resource(dev, SYS_RES_IRQ, 0, NULL, NULL) != 0; com->tx_fifo_size = 1; #ifdef PC98 com->obufsize = obufsize; com->obuf1 = obuf; com->obuf2 = obuf + obufsize; #endif com->obufs[0].l_head = com->obuf1; com->obufs[1].l_head = com->obuf2; #ifdef PC98 com->pc98_if_type = if_type; if (IS_8251(if_type)) { pc98_set_ioport(com); if (if_type == COM_IF_INTERNAL && pc98_check_8251fifo()) { com->pc98_8251fifo = 1; com->pc98_8251fifo_enable = 0; } } else { bus_addr_t *iat = if_16550a_type[if_type & 0x0f].iat; com->data_port = iobase + iat[com_data]; com->int_ctl_port = iobase + iat[com_ier]; com->int_id_port = iobase + iat[com_iir]; com->modem_ctl_port = iobase + iat[com_mcr]; com->mcr_image = inb(com->modem_ctl_port); com->line_status_port = iobase + iat[com_lsr]; com->modem_status_port = iobase + iat[com_msr]; } #else /* not PC98 */ com->data_port = iobase + com_data; com->int_ctl_port = iobase + com_ier; com->int_id_port = iobase + com_iir; com->modem_ctl_port = iobase + com_mcr; com->mcr_image = inb(com->modem_ctl_port); com->line_status_port = iobase + com_lsr; com->modem_status_port = iobase + com_msr; #endif #ifdef PC98 if (!IS_8251(if_type) && rclk == 0) rclk = if_16550a_type[if_type & 0x0f].rclk; #else if (rclk == 0) rclk = DEFAULT_RCLK; #endif com->rclk = rclk; /* * We don't use all the flags from 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. */ com->it_in.c_iflag = 0; com->it_in.c_oflag = 0; com->it_in.c_cflag = TTYDEF_CFLAG; com->it_in.c_lflag = 0; if (unit == comconsole) { #ifdef PC98 if (IS_8251(com->pc98_if_type)) DELAY(100000); #endif com->it_in.c_iflag = TTYDEF_IFLAG; com->it_in.c_oflag = TTYDEF_OFLAG; com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; com->it_in.c_lflag = TTYDEF_LFLAG; com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; com->lt_out.c_ispeed = com->lt_out.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; } else com->it_in.c_ispeed = com->it_in.c_ospeed = TTYDEF_SPEED; if (siosetwater(com, com->it_in.c_ispeed) != 0) { mtx_unlock_spin(&sio_lock); /* * Leave i/o resources allocated if this is a `cn'-level * console, so that other devices can't snarf them. */ if (iobase != siocniobase) bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return (ENOMEM); } mtx_unlock_spin(&sio_lock); termioschars(&com->it_in); com->it_out = com->it_in; /* attempt to determine UART type */ printf("sio%d: type", unit); #ifndef PC98 if (!COM_ISMULTIPORT(flags) && !COM_IIR_TXRDYBUG(flags) && !COM_NOSCR(flags)) { u_char scr; u_char scr1; u_char scr2; scr = sio_getreg(com, com_scr); sio_setreg(com, com_scr, 0xa5); scr1 = sio_getreg(com, com_scr); sio_setreg(com, com_scr, 0x5a); scr2 = sio_getreg(com, com_scr); sio_setreg(com, com_scr, scr); if (scr1 != 0xa5 || scr2 != 0x5a) { printf(" 8250 or not responding"); goto determined_type; } } #endif /* !PC98 */ #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if (com->pc98_8251fifo && !COM_NOFIFO(flags)) com->tx_fifo_size = 16; com_int_TxRx_disable( com ); com_cflag_and_speed_set( com, com->it_in.c_cflag, comdefaultrate ); com_tiocm_bic( com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE ); com_send_break_off( com ); if (com->pc98_if_type == COM_IF_INTERNAL) { printf(" (internal%s%s)", com->pc98_8251fifo ? " fifo" : "", PC98SIO_baud_rate_port(com->pc98_if_type) != -1 ? " v-fast" : ""); } else { printf(" 8251%s", if_8251_type[com->pc98_if_type & 0x0f].name); } } else { #endif /* PC98 */ sio_setreg(com, com_fifo, FIFO_ENABLE | FIFO_RX_HIGH); DELAY(100); switch (inb(com->int_id_port) & IIR_FIFO_MASK) { case FIFO_RX_LOW: printf(" 16450"); break; case FIFO_RX_MEDL: printf(" 16450?"); break; case FIFO_RX_MEDH: printf(" 16550?"); break; case FIFO_RX_HIGH: if (COM_NOFIFO(flags)) { printf(" 16550A fifo disabled"); break; } com->hasfifo = TRUE; #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) { com->tx_fifo_size = 2048; com->rsabase = iobase; outb(com->rsabase + rsa_ier, 0x00); outb(com->rsabase + rsa_frr, 0x00); } #else if (COM_ST16650A(flags)) { printf(" ST16650A"); com->st16650a = TRUE; com->tx_fifo_size = 32; break; } if (COM_TI16754(flags)) { printf(" TI16754"); com->tx_fifo_size = 64; break; } #endif printf(" 16550A"); #ifdef COM_ESP #ifdef PC98 if (com->pc98_if_type == COM_IF_ESP98) #endif for (espp = likely_esp_ports; *espp != 0; espp++) if (espattach(com, *espp)) { com->tx_fifo_size = 1024; break; } if (com->esp) break; #endif #ifdef PC98 com->tx_fifo_size = 16; #else com->tx_fifo_size = COM_FIFOSIZE(flags); if (com->tx_fifo_size == 0) com->tx_fifo_size = 16; else printf(" lookalike with %u bytes FIFO", com->tx_fifo_size); #endif break; } #ifdef PC98 if (com->pc98_if_type == COM_IF_RSB3000) { /* Set RSB-2000/3000 Extended Buffer mode. */ u_char lcr; lcr = sio_getreg(com, com_cfcr); sio_setreg(com, com_cfcr, lcr | CFCR_DLAB); sio_setreg(com, com_emr, EMR_EXBUFF | EMR_EFMODE); sio_setreg(com, com_cfcr, lcr); } #endif #ifdef COM_ESP if (com->esp) { /* * Set 16550 compatibility mode. * We don't use the ESP_MODE_SCALE bit to increase the * fifo trigger levels because we can't handle large * bursts of input. * XXX flow control should be set in comparam(), not here. */ #ifdef PC98 outb(com->esp_port + ESP98_CMD1, ESP_SETMODE); outb(com->esp_port + ESP98_CMD2, ESP_MODE_RTS | ESP_MODE_FIFO); #else outb(com->esp_port + ESP_CMD1, ESP_SETMODE); outb(com->esp_port + ESP_CMD2, ESP_MODE_RTS | ESP_MODE_FIFO); #endif /* Set RTS/CTS flow control. */ #ifdef PC98 outb(com->esp_port + ESP98_CMD1, ESP_SETFLOWTYPE); outb(com->esp_port + ESP98_CMD2, ESP_FLOW_RTS); outb(com->esp_port + ESP98_CMD2, ESP_FLOW_CTS); #else outb(com->esp_port + ESP_CMD1, ESP_SETFLOWTYPE); outb(com->esp_port + ESP_CMD2, ESP_FLOW_RTS); outb(com->esp_port + ESP_CMD2, ESP_FLOW_CTS); #endif /* Set flow-control levels. */ #ifdef PC98 outb(com->esp_port + ESP98_CMD1, ESP_SETRXFLOW); outb(com->esp_port + ESP98_CMD2, HIBYTE(768)); outb(com->esp_port + ESP98_CMD2, LOBYTE(768)); outb(com->esp_port + ESP98_CMD2, HIBYTE(512)); outb(com->esp_port + ESP98_CMD2, LOBYTE(512)); #else outb(com->esp_port + ESP_CMD1, ESP_SETRXFLOW); outb(com->esp_port + ESP_CMD2, HIBYTE(768)); outb(com->esp_port + ESP_CMD2, LOBYTE(768)); outb(com->esp_port + ESP_CMD2, HIBYTE(512)); outb(com->esp_port + ESP_CMD2, LOBYTE(512)); #endif #ifdef PC98 /* Set UART clock prescaler. */ outb(com->esp_port + ESP98_CMD1, ESP_SETCLOCK); outb(com->esp_port + ESP98_CMD2, 2); /* 4 times */ #endif } #endif /* COM_ESP */ sio_setreg(com, com_fifo, 0); #ifdef PC98 printf("%s", if_16550a_type[com->pc98_if_type & 0x0f].name); #else determined_type: ; #endif #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(flags)) { device_t masterdev; com->multiport = TRUE; printf(" (multiport"); if (unit == COM_MPMASTER(flags)) printf(" master"); printf(")"); masterdev = devclass_get_device(sio_devclass, COM_MPMASTER(flags)); com->no_irq = (masterdev == NULL || bus_get_resource(masterdev, SYS_RES_IRQ, 0, NULL, NULL) != 0); } #endif /* COM_MULTIPORT */ #ifdef PC98 } #endif if (unit == comconsole) printf(", console"); if (COM_IIR_TXRDYBUG(flags)) printf(" with a buggy IIR_TXRDY implementation"); printf("\n"); if (sio_fast_ih == NULL) { swi_add(&tty_ithd, "tty:sio", siopoll, NULL, SWI_TTY, 0, &sio_fast_ih); swi_add(&clk_ithd, "tty:sio", siopoll, NULL, SWI_TTY, 0, &sio_slow_ih); } minorbase = UNIT_TO_MINOR(unit); com->devs[0] = make_dev(&sio_cdevsw, minorbase, UID_ROOT, GID_WHEEL, 0600, "ttyd%r", unit); com->devs[1] = make_dev(&sio_cdevsw, minorbase | CONTROL_INIT_STATE, UID_ROOT, GID_WHEEL, 0600, "ttyid%r", unit); com->devs[2] = make_dev(&sio_cdevsw, minorbase | CONTROL_LOCK_STATE, UID_ROOT, GID_WHEEL, 0600, "ttyld%r", unit); com->devs[3] = make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK, UID_UUCP, GID_DIALER, 0660, "cuaa%r", unit); com->devs[4] = make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK | CONTROL_INIT_STATE, UID_UUCP, GID_DIALER, 0660, "cuaia%r", unit); com->devs[5] = make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK | CONTROL_LOCK_STATE, UID_UUCP, GID_DIALER, 0660, "cuala%r", unit); for (rid = 0; rid < 6; rid++) com->devs[rid]->si_drv1 = com; com->flags = flags; com->pps.ppscap = PPS_CAPTUREASSERT | PPS_CAPTURECLEAR; if (COM_PPSCTS(flags)) com->pps_bit = MSR_CTS; else com->pps_bit = MSR_DCD; pps_init(&com->pps); rid = 0; com->irqres = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (com->irqres) { ret = BUS_SETUP_INTR(device_get_parent(dev), dev, com->irqres, INTR_TYPE_TTY | INTR_FAST, siointr, com, &com->cookie); if (ret) { ret = BUS_SETUP_INTR(device_get_parent(dev), dev, com->irqres, INTR_TYPE_TTY, siointr, com, &com->cookie); if (ret == 0) device_printf(dev, "unable to activate interrupt in fast mode - using normal mode\n"); } if (ret) device_printf(dev, "could not activate interrupt\n"); #if defined(DDB) && (defined(BREAK_TO_DEBUGGER) || \ defined(ALT_BREAK_TO_DEBUGGER)) /* * Enable interrupts for early break-to-debugger support * on the console. */ if (ret == 0 && unit == comconsole) outb(siocniobase + com_ier, IER_ERXRDY | IER_ERLS | IER_EMSC); #endif } return (0); } static int sioopen(dev, flag, mode, td) dev_t dev; int flag; int mode; struct thread *td; { struct com_s *com; int error; int mynor; int s; struct tty *tp; int unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); if (com == NULL) return (ENXIO); if (com->gone) return (ENXIO); if (mynor & CONTROL_MASK) return (0); tp = dev->si_tty = com->tp = ttymalloc(com->tp); s = spltty(); /* * We jump to this label after all non-interrupted sleeps to pick * up any changes of the device state. */ open_top: while (com->state & CS_DTR_OFF) { error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "siodtr", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!com->active_out) { error = EBUSY; goto out; } } else { if (com->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&com->active_out, TTIPRI | PCATCH, "siobi", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && suser(td)) { 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 = comstart; tp->t_param = comparam; tp->t_stop = comstop; tp->t_dev = dev; tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in; #ifdef PC98 if (!IS_8251(com->pc98_if_type)) #endif (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); com->poll = com->no_irq; com->poll_output = com->loses_outints; ++com->wopeners; error = comparam(tp, &tp->t_termios); --com->wopeners; if (error != 0) goto out; #ifdef PC98 if (IS_8251(com->pc98_if_type)) { com_tiocm_bis(com, TIOCM_DTR|TIOCM_RTS); pc98_msrint_start(dev); if (com->pc98_8251fifo) { com->pc98_8251fifo_enable = 1; outb(I8251F_fcr, CTRL8251F_ENABLE | CTRL8251F_XMT_RST | CTRL8251F_RCV_RST); } } #endif /* * XXX we should goto open_top if comparam() slept. */ if (com->hasfifo) { int i; /* * (Re)enable and drain fifos. * * Certain SMC chips cause problems if the fifos * are enabled while input is ready. Turn off the * fifo if necessary to clear the input. We test * the input ready bit after enabling the fifos * since we've already enabled them in comparam() * and to handle races between enabling and fresh * input. */ for (i = 0; i < 500; i++) { sio_setreg(com, com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image); #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) outb(com->rsabase + rsa_frr , 0x00); #endif /* * XXX the delays are for superstitious * historical reasons. It must be less than * the character time at the maximum * supported speed (87 usec at 115200 bps * 8N1). Otherwise we might loop endlessly * if data is streaming in. We used to use * delays of 100. That usually worked * because DELAY(100) used to usually delay * for about 85 usec instead of 100. */ DELAY(50); #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III ? !(inb(com->rsabase + rsa_srr) & 0x08) : !(inb(com->line_status_port) & LSR_RXRDY)) break; #else if (!(inb(com->line_status_port) & LSR_RXRDY)) break; #endif sio_setreg(com, com_fifo, 0); DELAY(50); (void) inb(com->data_port); } if (i == 500) { error = EIO; goto out; } } mtx_lock_spin(&sio_lock); #ifdef PC98 if (IS_8251(com->pc98_if_type)) { com_tiocm_bis(com, TIOCM_LE); com->pc98_prev_modem_status = pc98_get_modem_status(com); com_int_Rx_enable(com); } else { #endif (void) inb(com->line_status_port); (void) inb(com->data_port); com->prev_modem_status = com->last_modem_status = inb(com->modem_status_port); outb(com->int_ctl_port, IER_ERXRDY | IER_ERLS | IER_EMSC | (COM_IIR_TXRDYBUG(com->flags) ? 0 : IER_ETXRDY)); #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) { outb(com->rsabase + rsa_ier, 0x1d); outb(com->int_ctl_port, IER_ERLS | IER_EMSC); } #endif #ifdef PC98 } #endif mtx_unlock_spin(&sio_lock); /* * Handle initial DCD. Callout devices get a fake initial * DCD (trapdoor DCD). If we are callout, then any sleeping * callin opens get woken up and resume sleeping on "siobi" * instead of "siodcd". */ /* * XXX `mynor & CALLOUT_MASK' should be * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where * TRAPDOOR_CARRIER is the default initial state for callout * devices and SOFT_CARRIER is like CLOCAL except it hides * the true carrier. */ #ifdef PC98 if ((IS_8251(com->pc98_if_type) && (pc98_get_modem_status(com) & TIOCM_CAR)) || (!IS_8251(com->pc98_if_type) && (com->prev_modem_status & MSR_DCD)) || mynor & CALLOUT_MASK) ttyld_modem(tp, 1); #else if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) ttyld_modem(tp, 1); #endif } /* * Wait for DCD if necessary. */ if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++com->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "siodcd", 0); if (com_addr(unit) == NULL) return (ENXIO); --com->wopeners; if (error != 0 || com->gone) goto out; goto open_top; } error = ttyld_open(tp, dev); - disc_optim(tp, &tp->t_termios, com); + com->hotchar = ttyldoptim(tp); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) com->active_out = TRUE; siosettimeout(); out: splx(s); if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); return (error); } static int sioclose(dev, flag, mode, td) dev_t dev; int flag; int mode; struct thread *td; { struct com_s *com; int mynor; int s; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (0); com = com_addr(MINOR_TO_UNIT(mynor)); if (com == NULL) return (ENODEV); tp = com->tp; s = spltty(); ttyld_close(tp, flag); #ifdef PC98 com->modem_checking = 0; #endif - disc_optim(tp, &tp->t_termios, com); + com->hotchar = ttyldoptim(tp); comhardclose(com); ttyclose(tp); siosettimeout(); splx(s); if (com->gone) { printf("sio%d: gone\n", com->unit); s = spltty(); if (com->ibuf != NULL) free(com->ibuf, M_DEVBUF); bzero(tp, sizeof *tp); splx(s); } return (0); } static void comhardclose(com) struct com_s *com; { int s; struct tty *tp; s = spltty(); com->poll = FALSE; com->poll_output = FALSE; com->do_timestamp = FALSE; com->do_dcd_timestamp = FALSE; com->pps.ppsparam.mode = 0; #ifdef PC98 if (IS_8251(com->pc98_if_type)) com_send_break_off(com); else #endif sio_setreg(com, com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); tp = com->tp; #if defined(DDB) && (defined(BREAK_TO_DEBUGGER) || \ defined(ALT_BREAK_TO_DEBUGGER)) /* * Leave interrupts enabled and don't clear DTR if this is the * console. This allows us to detect break-to-debugger events * while the console device is closed. */ if (com->unit != comconsole) #endif { #ifdef PC98 int tmp; if (IS_8251(com->pc98_if_type)) com_int_TxRx_disable(com); else sio_setreg(com, com_ier, 0); if (com->pc98_if_type == COM_IF_RSA98III) outb(com->rsabase + rsa_ier, 0x00); if (IS_8251(com->pc98_if_type)) tmp = pc98_get_modem_status(com) & TIOCM_CAR; else tmp = com->prev_modem_status & MSR_DCD; #else sio_setreg(com, com_ier, 0); #endif if (tp->t_cflag & HUPCL /* * XXX we will miss any carrier drop between here and the * next open. Perhaps we should watch DCD even when the * port is closed; it is not sufficient to check it at * the next open because it might go up and down while * we're not watching. */ || (!com->active_out #ifdef PC98 && !(tmp) #else && !(com->prev_modem_status & MSR_DCD) #endif && !(com->it_in.c_cflag & CLOCAL)) || !(tp->t_state & TS_ISOPEN)) { #ifdef PC98 if (IS_8251(com->pc98_if_type)) com_tiocm_bic(com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE); else #endif (void)commctl(com, TIOCM_DTR, DMBIC); if (com->dtr_wait != 0 && !(com->state & CS_DTR_OFF)) { timeout(siodtrwakeup, com, com->dtr_wait); com->state |= CS_DTR_OFF; } } #ifdef PC98 else { if (IS_8251(com->pc98_if_type)) com_tiocm_bic(com, TIOCM_LE); } #endif } #ifdef PC98 if (com->pc98_8251fifo) { if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_XMT_RST | CTRL8251F_RCV_RST); com->pc98_8251fifo_enable = 0; } #endif if (com->hasfifo) { /* * Disable fifos so that they are off after controlled * reboots. Some BIOSes fail to detect 16550s when the * fifos are enabled. */ sio_setreg(com, com_fifo, 0); } com->active_out = FALSE; wakeup(&com->active_out); wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } static int sioread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct com_s *com; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); com = com_addr(MINOR_TO_UNIT(mynor)); if (com == NULL || com->gone) return (ENODEV); return (ttyld_read(com->tp, uio, flag)); } static int siowrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct com_s *com; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); if (com == NULL || com->gone) return (ENODEV); /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that * is not the console. In that situation we don't need/want the X * server taking over the console. */ if (constty != NULL && unit == comconsole) constty = NULL; return (ttyld_write(com->tp, uio, flag)); } static void siobusycheck(chan) void *chan; { struct com_s *com; int s; com = (struct com_s *)chan; /* * Clear TS_BUSY if low-level output is complete. * spl locking is sufficient because siointr1() does not set CS_BUSY. * If siointr1() clears CS_BUSY after we look at it, then we'll get * called again. Reading the line status port outside of siointr1() * is safe because CS_BUSY is clear so there are no output interrupts * to lose. */ s = spltty(); if (com->state & CS_BUSY) com->extra_state &= ~CSE_BUSYCHECK; /* False alarm. */ #ifdef PC98 else if ((IS_8251(com->pc98_if_type) && ((com->pc98_8251fifo_enable && (inb(I8251F_lsr) & (STS8251F_TxRDY | STS8251F_TxEMP)) == (STS8251F_TxRDY | STS8251F_TxEMP)) || (!com->pc98_8251fifo_enable && (inb(com->sts_port) & (STS8251_TxRDY | STS8251_TxEMP)) == (STS8251_TxRDY | STS8251_TxEMP)))) || ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY))) { #else else if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY)) { #endif com->tp->t_state &= ~TS_BUSY; ttwwakeup(com->tp); com->extra_state &= ~CSE_BUSYCHECK; } else timeout(siobusycheck, com, hz / 100); splx(s); } static u_int siodivisor(rclk, speed) u_long rclk; speed_t speed; { long actual_speed; u_int divisor; int error; if (speed == 0) return (0); #if UINT_MAX > (ULONG_MAX - 1) / 8 if (speed > (ULONG_MAX - 1) / 8) return (0); #endif divisor = (rclk / (8UL * speed) + 1) / 2; if (divisor == 0 || divisor >= 65536) return (0); actual_speed = rclk / (16UL * divisor); /* 10 times error in percent: */ error = ((actual_speed - (long)speed) * 2000 / (long)speed + 1) / 2; /* 3.0% maximum error tolerance: */ if (error < -30 || error > 30) return (0); return (divisor); } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } /* * Call this function with the sio_lock mutex held. It will return with the * lock still held. */ static void sioinput(com) struct com_s *com; { u_char *buf; int incc; u_char line_status; int recv_data; struct tty *tp; buf = com->ibuf; tp = com->tp; if (!(tp->t_state & TS_ISOPEN) || !(tp->t_cflag & CREAD)) { com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; return; } if (tp->t_state & TS_CAN_BYPASS_L_RINT) { /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ do { /* * This may look odd, but it is using save-and-enable * semantics instead of the save-and-disable semantics * that are used everywhere else. */ mtx_unlock_spin(&sio_lock); incc = com->iptr - buf; if (tp->t_rawq.c_cc + incc > tp->t_ihiwat && (com->state & CS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); com->delta_error_counts[CE_TTY_BUF_OVERFLOW] += b_to_q((char *)buf, incc, &tp->t_rawq); buf += incc; tk_nin += incc; tk_rawcc += incc; tp->t_rawcc += incc; ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; comstart(tp); } mtx_lock_spin(&sio_lock); } while (buf < com->iptr); } else { do { /* * This may look odd, but it is using save-and-enable * semantics instead of the save-and-disable semantics * that are used everywhere else. */ mtx_unlock_spin(&sio_lock); line_status = buf[com->ierroff]; recv_data = *buf++; if (line_status & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { if (line_status & LSR_BI) recv_data |= TTY_BI; if (line_status & LSR_FE) recv_data |= TTY_FE; if (line_status & LSR_OE) recv_data |= TTY_OE; if (line_status & LSR_PE) recv_data |= TTY_PE; } ttyld_rint(tp, recv_data); mtx_lock_spin(&sio_lock); } while (buf < com->iptr); } com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; /* * There is now room for another low-level buffer full of input, * so enable RTS if it is now disabled and there is room in the * high-level buffer. */ #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if ((com->state & CS_RTS_IFLOW) && !(com_tiocm_get(com) & TIOCM_RTS) && !(tp->t_state & TS_TBLOCK)) com_tiocm_bis(com, TIOCM_RTS); } else { if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } #else if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #endif } static void siointr(arg) void *arg; { struct com_s *com; #if defined(PC98) && defined(COM_MULTIPORT) u_char rsa_buf_status; #endif #ifndef COM_MULTIPORT com = (struct com_s *)arg; mtx_lock_spin(&sio_lock); siointr1(com); mtx_unlock_spin(&sio_lock); #else /* COM_MULTIPORT */ bool_t possibly_more_intrs; int unit; /* * Loop until there is no activity on any port. This is necessary * to get an interrupt edge more than to avoid another interrupt. * If the IRQ signal is just an OR of the IRQ signals from several * devices, then the edge from one may be lost because another is * on. */ mtx_lock_spin(&sio_lock); do { possibly_more_intrs = FALSE; for (unit = 0; unit < sio_numunits; ++unit) { com = com_addr(unit); /* * XXX COM_LOCK(); * would it work here, or be counter-productive? */ #ifdef PC98 if (com != NULL && !com->gone && IS_8251(com->pc98_if_type)) { siointr1(com); } else if (com != NULL && !com->gone && com->pc98_if_type == COM_IF_RSA98III) { rsa_buf_status = inb(com->rsabase + rsa_srr) & 0xc9; if ((rsa_buf_status & 0xc8) || !(rsa_buf_status & 0x01)) { siointr1(com); if (rsa_buf_status != (inb(com->rsabase + rsa_srr) & 0xc9)) possibly_more_intrs = TRUE; } } else #endif if (com != NULL && !com->gone && (inb(com->int_id_port) & IIR_IMASK) != IIR_NOPEND) { siointr1(com); possibly_more_intrs = TRUE; } /* XXX COM_UNLOCK(); */ } } while (possibly_more_intrs); mtx_unlock_spin(&sio_lock); #endif /* COM_MULTIPORT */ } static struct timespec siots[8]; static int siotso; static int volatile siotsunit = -1; static int sysctl_siots(SYSCTL_HANDLER_ARGS) { char buf[128]; long long delta; size_t len; int error, i, tso; for (i = 1, tso = siotso; i < tso; i++) { delta = (long long)(siots[i].tv_sec - siots[i - 1].tv_sec) * 1000000000 + (siots[i].tv_nsec - siots[i - 1].tv_nsec); len = sprintf(buf, "%lld\n", delta); if (delta >= 110000) len += sprintf(buf + len - 1, ": *** %ld.%09ld\n", (long)siots[i].tv_sec, siots[i].tv_nsec) - 1; if (i == tso - 1) buf[len - 1] = '\0'; error = SYSCTL_OUT(req, buf, len); if (error != 0) return (error); uio_yield(); } return (0); } SYSCTL_PROC(_machdep, OID_AUTO, siots, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, sysctl_siots, "A", "sio timestamps"); static void siointr1(com) struct com_s *com; { u_char int_ctl; u_char int_ctl_new; u_char line_status; u_char modem_status; u_char *ioptr; u_char recv_data; #ifdef PC98 u_char tmp = 0; u_char rsa_buf_status = 0; int rsa_tx_fifo_size = 0; #endif /* PC98 */ if (COM_IIR_TXRDYBUG(com->flags)) { int_ctl = inb(com->int_ctl_port); int_ctl_new = int_ctl; } else { int_ctl = 0; int_ctl_new = 0; } while (!com->gone) { #ifdef PC98 status_read:; if (IS_8251(com->pc98_if_type)) { if (com->pc98_8251fifo_enable) tmp = inb(I8251F_lsr); else tmp = inb(com->sts_port); more_intr: line_status = 0; if (com->pc98_8251fifo_enable) { if (tmp & STS8251F_TxRDY) line_status |= LSR_TXRDY; if (tmp & STS8251F_RxRDY) line_status |= LSR_RXRDY; if (tmp & STS8251F_TxEMP) line_status |= LSR_TSRE; if (tmp & STS8251F_PE) line_status |= LSR_PE; if (tmp & STS8251F_OE) line_status |= LSR_OE; if (tmp & STS8251F_BD_SD) line_status |= LSR_BI; } else { if (tmp & STS8251_TxRDY) line_status |= LSR_TXRDY; if (tmp & STS8251_RxRDY) line_status |= LSR_RXRDY; if (tmp & STS8251_TxEMP) line_status |= LSR_TSRE; if (tmp & STS8251_PE) line_status |= LSR_PE; if (tmp & STS8251_OE) line_status |= LSR_OE; if (tmp & STS8251_FE) line_status |= LSR_FE; if (tmp & STS8251_BD_SD) line_status |= LSR_BI; } } else { #endif /* PC98 */ if (com->pps.ppsparam.mode & PPS_CAPTUREBOTH) { modem_status = inb(com->modem_status_port); if ((modem_status ^ com->last_modem_status) & com->pps_bit) { pps_capture(&com->pps); pps_event(&com->pps, (modem_status & com->pps_bit) ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR); } } line_status = inb(com->line_status_port); #ifdef PC98 } if (com->pc98_if_type == COM_IF_RSA98III) rsa_buf_status = inb(com->rsabase + rsa_srr); #endif /* PC98 */ /* input event? (check first to help avoid overruns) */ #ifndef PC98 while (line_status & LSR_RCV_MASK) { #else while ((line_status & LSR_RCV_MASK) || (com->pc98_if_type == COM_IF_RSA98III && (rsa_buf_status & 0x08))) { #endif /* PC98 */ /* break/unnattached error bits or real input? */ #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if (com->pc98_8251fifo_enable) { recv_data = inb(I8251F_data); if (tmp & (STS8251F_PE | STS8251F_OE | STS8251F_BD_SD)) { pc98_i8251_or_cmd(com, CMD8251_ER); recv_data = 0; } } else { recv_data = inb(com->data_port); if (tmp & (STS8251_PE | STS8251_OE | STS8251_FE | STS8251_BD_SD)) { pc98_i8251_or_cmd(com, CMD8251_ER); recv_data = 0; } } } else if (com->pc98_if_type == COM_IF_RSA98III) { if (!(rsa_buf_status & 0x08)) recv_data = 0; else recv_data = inb(com->data_port); } else #endif if (!(line_status & LSR_RXRDY)) recv_data = 0; else recv_data = inb(com->data_port); #ifdef DDB #ifdef ALT_BREAK_TO_DEBUGGER if (com->unit == comconsole && db_alt_break(recv_data, &com->alt_brk_state) != 0) breakpoint(); #endif /* ALT_BREAK_TO_DEBUGGER */ #endif /* DDB */ if (line_status & (LSR_BI | LSR_FE | LSR_PE)) { /* * Don't store BI if IGNBRK or FE/PE if IGNPAR. * Otherwise, push the work to a higher level * (to handle PARMRK) if we're bypassing. * Otherwise, convert BI/FE and PE+INPCK to 0. * * This makes bypassing work right in the * usual "raw" case (IGNBRK set, and IGNPAR * and INPCK clear). * * Note: BI together with FE/PE means just BI. */ if (line_status & LSR_BI) { #if defined(DDB) && defined(BREAK_TO_DEBUGGER) if (com->unit == comconsole) { breakpoint(); goto cont; } #endif if (com->tp == NULL || com->tp->t_iflag & IGNBRK) goto cont; } else { if (com->tp == NULL || com->tp->t_iflag & IGNPAR) goto cont; } if (com->tp->t_state & TS_CAN_BYPASS_L_RINT && (line_status & (LSR_BI | LSR_FE) || com->tp->t_iflag & INPCK)) recv_data = 0; } ++com->bytes_in; if (com->hotchar != 0 && recv_data == com->hotchar) swi_sched(sio_fast_ih, 0); ioptr = com->iptr; if (ioptr >= com->ibufend) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { if (com->do_timestamp) microtime(&com->timestamp); ++com_events; swi_sched(sio_slow_ih, SWI_DELAY); #if 0 /* for testing input latency vs efficiency */ if (com->iptr - com->ibuf == 8) swi_sched(sio_fast_ih, 0); #endif ioptr[0] = recv_data; ioptr[com->ierroff] = line_status; com->iptr = ++ioptr; if (ioptr == com->ihighwater && com->state & CS_RTS_IFLOW) #ifdef PC98 IS_8251(com->pc98_if_type) ? com_tiocm_bic(com, TIOCM_RTS) : #endif outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); if (line_status & LSR_OE) CE_RECORD(com, CE_OVERRUN); } cont: if (line_status & LSR_TXRDY && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) goto txrdy; /* * "& 0x7F" is to avoid the gcc-1.40 generating a slow * jump from the top of the loop to here */ #ifdef PC98 if (IS_8251(com->pc98_if_type)) goto status_read; else #endif line_status = inb(com->line_status_port) & 0x7F; #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) rsa_buf_status = inb(com->rsabase + rsa_srr); #endif /* PC98 */ } /* modem status change? (always check before doing output) */ #ifdef PC98 if (!IS_8251(com->pc98_if_type)) { #endif modem_status = inb(com->modem_status_port); if (modem_status != com->last_modem_status) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * Schedule high level to handle DCD changes. Note * that we don't use the delta bits anywhere. Some * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; swi_sched(sio_fast_ih, 0); } /* handle CTS change immediately for crisp flow ctl */ if (com->state & CS_CTS_OFLOW) { if (modem_status & MSR_CTS) com->state |= CS_ODEVREADY; else com->state &= ~CS_ODEVREADY; } } #ifdef PC98 } #endif txrdy: /* output queued and everything ready? */ #ifndef PC98 if (line_status & LSR_TXRDY && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { #else if (((com->pc98_if_type == COM_IF_RSA98III) ? (rsa_buf_status & 0x02) : (line_status & LSR_TXRDY)) && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { #endif #ifdef PC98 Port_t tmp_data_port; if (IS_8251(com->pc98_if_type) && com->pc98_8251fifo_enable) tmp_data_port = I8251F_data; else tmp_data_port = com->data_port; #endif ioptr = com->obufq.l_head; if (com->tx_fifo_size > 1 && com->unit != siotsunit) { u_int ocount; ocount = com->obufq.l_tail - ioptr; #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) { rsa_buf_status = inb(com->rsabase + rsa_srr); rsa_tx_fifo_size = 1024; if (!(rsa_buf_status & 0x01)) rsa_tx_fifo_size = 2048; if (ocount > rsa_tx_fifo_size) ocount = rsa_tx_fifo_size; } else #endif if (ocount > com->tx_fifo_size) ocount = com->tx_fifo_size; com->bytes_out += ocount; do #ifdef PC98 outb(tmp_data_port, *ioptr++); #else outb(com->data_port, *ioptr++); #endif while (--ocount != 0); } else { #ifdef PC98 outb(tmp_data_port, *ioptr++); #else outb(com->data_port, *ioptr++); #endif ++com->bytes_out; if (com->unit == siotsunit && siotso < sizeof siots / sizeof siots[0]) nanouptime(&siots[siotso++]); } #ifdef PC98 if (IS_8251(com->pc98_if_type)) if (!(pc98_check_i8251_interrupt(com) & IEN_TxFLAG)) com_int_Tx_enable(com); #endif com->obufq.l_head = ioptr; if (COM_IIR_TXRDYBUG(com->flags)) int_ctl_new = int_ctl | IER_ETXRDY; if (ioptr >= com->obufq.l_tail) { struct lbq *qp; qp = com->obufq.l_next; qp->l_queued = FALSE; qp = qp->l_next; if (qp != NULL) { com->obufq.l_head = qp->l_head; com->obufq.l_tail = qp->l_tail; com->obufq.l_next = qp; } else { /* output just completed */ if (COM_IIR_TXRDYBUG(com->flags)) int_ctl_new = int_ctl & ~IER_ETXRDY; com->state &= ~CS_BUSY; #if defined(PC98) if (IS_8251(com->pc98_if_type) && pc98_check_i8251_interrupt(com) & IEN_TxFLAG) com_int_Tx_disable(com); #endif } if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; /* handle at high level ASAP */ swi_sched(sio_fast_ih, 0); } } #ifdef PC98 if (COM_IIR_TXRDYBUG(com->flags) && int_ctl != int_ctl_new) { if (com->pc98_if_type == COM_IF_RSA98III) { int_ctl_new &= ~(IER_ETXRDY | IER_ERXRDY); outb(com->int_ctl_port, int_ctl_new); outb(com->rsabase + rsa_ier, 0x1d); } else outb(com->int_ctl_port, int_ctl_new); } #else if (COM_IIR_TXRDYBUG(com->flags) && int_ctl != int_ctl_new) outb(com->int_ctl_port, int_ctl_new); #endif } #ifdef PC98 else if (line_status & LSR_TXRDY) { if (IS_8251(com->pc98_if_type)) if (pc98_check_i8251_interrupt(com) & IEN_TxFLAG) com_int_Tx_disable(com); } if (IS_8251(com->pc98_if_type)) { if (com->pc98_8251fifo_enable) { if ((tmp = inb(I8251F_lsr)) & STS8251F_RxRDY) goto more_intr; } else { if ((tmp = inb(com->sts_port)) & STS8251_RxRDY) goto more_intr; } } #endif /* finished? */ #ifndef COM_MULTIPORT #ifdef PC98 if (IS_8251(com->pc98_if_type)) return; #endif if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) #endif /* COM_MULTIPORT */ return; } } static int sioioctl(dev, cmd, data, flag, td) dev_t dev; u_long cmd; caddr_t data; int flag; struct thread *td; { struct com_s *com; int error; int mynor; int s; struct tty *tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) u_long oldcmd; struct termios term; #endif mynor = minor(dev); com = com_addr(MINOR_TO_UNIT(mynor)); if (com == NULL || com->gone) return (ENODEV); if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->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); default: return (ENOTTY); } } tp = com->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 *)data; struct termios *lt = mynor & CALLOUT_MASK ? &com->lt_out : &com->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); 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 = ttyioctl(dev, cmd, data, flag, td); - disc_optim(tp, &tp->t_termios, com); + com->hotchar = ttyldoptim(tp); if (error != ENOTTY) return (error); s = spltty(); #ifdef PC98 if (IS_8251(com->pc98_if_type)) { switch (cmd) { case TIOCSBRK: com_send_break_on(com); break; case TIOCCBRK: com_send_break_off(com); break; case TIOCSDTR: com_tiocm_bis(com, TIOCM_DTR | TIOCM_RTS); break; case TIOCCDTR: com_tiocm_bic(com, TIOCM_DTR); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: com_tiocm_set(com, *(int *)data); break; case TIOCMBIS: com_tiocm_bis(com, *(int *)data); break; case TIOCMBIC: com_tiocm_bic(com, *(int *)data); break; case TIOCMGET: *(int *)data = com_tiocm_get(com); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(td); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); error = pps_ioctl(cmd, data, &com->pps); if (error == ENODEV) error = ENOTTY; return (error); } } else { #endif switch (cmd) { case TIOCSBRK: sio_setreg(com, com_cfcr, com->cfcr_image |= CFCR_SBREAK); break; case TIOCCBRK: sio_setreg(com, com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); break; case TIOCSDTR: (void)commctl(com, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void)commctl(com, TIOCM_DTR, DMBIC); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: (void)commctl(com, *(int *)data, DMSET); break; case TIOCMBIS: (void)commctl(com, *(int *)data, DMBIS); break; case TIOCMBIC: (void)commctl(com, *(int *)data, DMBIC); break; case TIOCMGET: *(int *)data = commctl(com, 0, DMGET); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(td); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); error = pps_ioctl(cmd, data, &com->pps); if (error == ENODEV) error = ENOTTY; return (error); } #ifdef PC98 } #endif splx(s); return (0); } /* software interrupt handler for SWI_TTY */ static void siopoll(void *dummy) { int unit; if (com_events == 0) return; repeat: for (unit = 0; unit < sio_numunits; ++unit) { struct com_s *com; int incc; struct tty *tp; com = com_addr(unit); if (com == NULL) continue; tp = com->tp; if (tp == NULL || com->gone) { /* * Discard any events related to never-opened or * going-away devices. */ mtx_lock_spin(&sio_lock); incc = com->iptr - com->ibuf; com->iptr = com->ibuf; if (com->state & CS_CHECKMSR) { incc += LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; } com_events -= incc; mtx_unlock_spin(&sio_lock); continue; } if (com->iptr != com->ibuf) { mtx_lock_spin(&sio_lock); sioinput(com); mtx_unlock_spin(&sio_lock); } if (com->state & CS_CHECKMSR) { u_char delta_modem_status; #ifdef PC98 if (!IS_8251(com->pc98_if_type)) { #endif mtx_lock_spin(&sio_lock); delta_modem_status = com->last_modem_status ^ com->prev_modem_status; com->prev_modem_status = com->last_modem_status; com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; mtx_unlock_spin(&sio_lock); if (delta_modem_status & MSR_DCD) ttyld_modem(tp, com->prev_modem_status & MSR_DCD); #ifdef PC98 } #endif } if (com->state & CS_ODONE) { mtx_lock_spin(&sio_lock); com_events -= LOTS_OF_EVENTS; com->state &= ~CS_ODONE; mtx_unlock_spin(&sio_lock); if (!(com->state & CS_BUSY) && !(com->extra_state & CSE_BUSYCHECK)) { timeout(siobusycheck, com, hz / 100); com->extra_state |= CSE_BUSYCHECK; } ttyld_start(tp); } if (com_events == 0) break; } if (com_events >= LOTS_OF_EVENTS) goto repeat; } static int comparam(tp, t) struct tty *tp; struct termios *t; { u_int cfcr; int cflag; struct com_s *com; u_int divisor; u_char dlbh; u_char dlbl; u_char efr_flowbits; int s; int unit; #ifdef PC98 u_char param = 0; #endif unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); if (com == NULL) return (ENODEV); #ifdef PC98 cfcr = 0; if (IS_8251(com->pc98_if_type)) { if (pc98_ttspeedtab(com, t->c_ospeed, &divisor) != 0) return (EINVAL); } else { #endif /* check requested parameters */ if (t->c_ispeed != (t->c_ospeed != 0 ? t->c_ospeed : tp->t_ospeed)) return (EINVAL); divisor = siodivisor(com->rclk, t->c_ispeed); if (divisor == 0) return (EINVAL); #ifdef PC98 } #endif /* parameters are OK, convert them to the com struct and the device */ s = spltty(); #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if (t->c_ospeed == 0) com_tiocm_bic(com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE); else com_tiocm_bis(com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE); } else #endif if (t->c_ospeed == 0) (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ else (void)commctl(com, TIOCM_DTR, DMBIS); cflag = t->c_cflag; #ifdef PC98 if (!IS_8251(com->pc98_if_type)) { #endif switch (cflag & CSIZE) { case CS5: cfcr = CFCR_5BITS; break; case CS6: cfcr = CFCR_6BITS; break; case CS7: cfcr = CFCR_7BITS; break; default: cfcr = CFCR_8BITS; break; } if (cflag & PARENB) { cfcr |= CFCR_PENAB; if (!(cflag & PARODD)) cfcr |= CFCR_PEVEN; } if (cflag & CSTOPB) cfcr |= CFCR_STOPB; if (com->hasfifo) { /* * Use a fifo trigger level low enough so that the input * latency from the fifo is less than about 16 msec and * the total latency is less than about 30 msec. These * latencies are reasonable for humans. Serial comms * protocols shouldn't expect anything better since modem * latencies are larger. * * The fifo trigger level cannot be set at RX_HIGH for high * speed connections without further work on reducing * interrupt disablement times in other parts of the system, * without producing silo overflow errors. */ com->fifo_image = com->unit == siotsunit ? 0 : t->c_ispeed <= 4800 ? FIFO_ENABLE : FIFO_ENABLE | FIFO_RX_MEDH; #ifdef COM_ESP /* * The Hayes ESP card needs the fifo DMA mode bit set * in compatibility mode. If not, it will interrupt * for each character received. */ if (com->esp) com->fifo_image |= FIFO_DMA_MODE; #endif sio_setreg(com, com_fifo, com->fifo_image); } #ifdef PC98 } #endif /* * This returns with interrupts disabled so that we can complete * the speed change atomically. Keeping interrupts disabled is * especially important while com_data is hidden. */ (void) siosetwater(com, t->c_ispeed); #ifdef PC98 if (IS_8251(com->pc98_if_type)) com_cflag_and_speed_set(com, cflag, t->c_ospeed); else { #endif sio_setreg(com, com_cfcr, cfcr | CFCR_DLAB); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (UMC8669F), setting them while input * is arriving loses sync until data stops arriving. */ dlbl = divisor & 0xFF; if (sio_getreg(com, com_dlbl) != dlbl) sio_setreg(com, com_dlbl, dlbl); dlbh = divisor >> 8; if (sio_getreg(com, com_dlbh) != dlbh) sio_setreg(com, com_dlbh, dlbh); #ifdef PC98 } #endif efr_flowbits = 0; if (cflag & CRTS_IFLOW) { com->state |= CS_RTS_IFLOW; efr_flowbits |= EFR_AUTORTS; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ #ifdef PC98 if (IS_8251(com->pc98_if_type)) com_tiocm_bis(com, TIOCM_RTS); else outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #endif } /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state |= CS_ODEVREADY; com->state &= ~CS_CTS_OFLOW; #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) { param = inb(com->rsabase + rsa_msr); outb(com->rsabase + rsa_msr, param & 0x14); } #endif if (cflag & CCTS_OFLOW) { com->state |= CS_CTS_OFLOW; efr_flowbits |= EFR_AUTOCTS; #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if (!(pc98_get_modem_status(com) & TIOCM_CTS)) com->state &= ~CS_ODEVREADY; } else if (com->pc98_if_type == COM_IF_RSA98III) { /* Set automatic flow control mode */ outb(com->rsabase + rsa_msr, param | 0x08); } else #endif if (!(com->last_modem_status & MSR_CTS)) com->state &= ~CS_ODEVREADY; } #ifdef PC98 if (!IS_8251(com->pc98_if_type)) sio_setreg(com, com_cfcr, com->cfcr_image = cfcr); #else if (com->st16650a) { sio_setreg(com, com_lcr, LCR_EFR_ENABLE); sio_setreg(com, com_efr, (sio_getreg(com, com_efr) & ~(EFR_AUTOCTS | EFR_AUTORTS)) | efr_flowbits); } sio_setreg(com, com_cfcr, com->cfcr_image = cfcr); #endif /* XXX shouldn't call functions while intrs are disabled. */ - disc_optim(tp, t, com); + com->hotchar = ttyldoptim(tp); mtx_unlock_spin(&sio_lock); splx(s); comstart(tp); if (com->ibufold != NULL) { free(com->ibufold, M_DEVBUF); com->ibufold = NULL; } return (0); } /* * This function must be called with the sio_lock mutex released and will * return with it obtained. */ static int siosetwater(com, speed) struct com_s *com; speed_t speed; { int cp4ticks; u_char *ibuf; int ibufsize; struct tty *tp; /* * Make the buffer size large enough to handle a softtty interrupt * latency of about 2 ticks without loss of throughput or data * (about 3 ticks if input flow control is not used or not honoured, * but a bit less for CS5-CS7 modes). */ cp4ticks = speed / 10 / hz * 4; for (ibufsize = 128; ibufsize < cp4ticks;) ibufsize <<= 1; #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) ibufsize = 2048; #endif if (ibufsize == com->ibufsize) { mtx_lock_spin(&sio_lock); return (0); } /* * Allocate input buffer. The extra factor of 2 in the size is * to allow for an error byte for each input byte. */ ibuf = malloc(2 * ibufsize, M_DEVBUF, M_NOWAIT); if (ibuf == NULL) { mtx_lock_spin(&sio_lock); return (ENOMEM); } /* Initialize non-critical variables. */ com->ibufold = com->ibuf; com->ibufsize = ibufsize; tp = com->tp; if (tp != NULL) { tp->t_ififosize = 2 * ibufsize; tp->t_ispeedwat = (speed_t)-1; tp->t_ospeedwat = (speed_t)-1; } /* * Read current input buffer, if any. Continue with interrupts * disabled. */ mtx_lock_spin(&sio_lock); if (com->iptr != com->ibuf) sioinput(com); /*- * Initialize critical variables, including input buffer watermarks. * The external device is asked to stop sending when the buffer * exactly reaches high water, or when the high level requests it. * The high level is notified immediately (rather than at a later * clock tick) when this watermark is reached. * The buffer size is chosen so the watermark should almost never * be reached. * The low watermark is invisibly 0 since the buffer is always * emptied all at once. */ com->iptr = com->ibuf = ibuf; com->ibufend = ibuf + ibufsize; com->ierroff = ibufsize; com->ihighwater = ibuf + 3 * ibufsize / 4; return (0); } static void comstart(tp) struct tty *tp; { struct com_s *com; int s; int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); if (com == NULL) return; s = spltty(); mtx_lock_spin(&sio_lock); if (tp->t_state & TS_TTSTOP) com->state &= ~CS_TTGO; else com->state |= CS_TTGO; if (tp->t_state & TS_TBLOCK) { #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if ((com_tiocm_get(com) & TIOCM_RTS) && (com->state & CS_RTS_IFLOW)) com_tiocm_bic(com, TIOCM_RTS); } else { if ((com->mcr_image & MCR_RTS) && (com->state & CS_RTS_IFLOW)) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); } #else if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #endif } else { #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if (!(com_tiocm_get(com) & TIOCM_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) com_tiocm_bis(com, TIOCM_RTS); } else { if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } #else if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #endif } mtx_unlock_spin(&sio_lock); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(s); return; } if (tp->t_outq.c_cc != 0) { struct lbq *qp; struct lbq *next; if (!com->obufs[0].l_queued) { com->obufs[0].l_tail = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, #ifdef PC98 com->obufsize); #else sizeof com->obuf1); #endif com->obufs[0].l_next = NULL; com->obufs[0].l_queued = TRUE; mtx_lock_spin(&sio_lock); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[0]; } else { com->obufq.l_head = com->obufs[0].l_head; com->obufq.l_tail = com->obufs[0].l_tail; com->obufq.l_next = &com->obufs[0]; com->state |= CS_BUSY; } mtx_unlock_spin(&sio_lock); } if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { com->obufs[1].l_tail = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, #ifdef PC98 com->obufsize); #else sizeof com->obuf2); #endif com->obufs[1].l_next = NULL; com->obufs[1].l_queued = TRUE; mtx_lock_spin(&sio_lock); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[1]; } else { com->obufq.l_head = com->obufs[1].l_head; com->obufq.l_tail = com->obufs[1].l_tail; com->obufq.l_next = &com->obufs[1]; com->state |= CS_BUSY; } mtx_unlock_spin(&sio_lock); } tp->t_state |= TS_BUSY; } mtx_lock_spin(&sio_lock); if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); /* fake interrupt to start output */ mtx_unlock_spin(&sio_lock); ttwwakeup(tp); splx(s); } static void comstop(tp, rw) struct tty *tp; int rw; { struct com_s *com; #ifdef PC98 int rsa98_tmp = 0; #endif com = com_addr(DEV_TO_UNIT(tp->t_dev)); if (com == NULL || com->gone) return; mtx_lock_spin(&sio_lock); if (rw & FWRITE) { #ifdef PC98 if (!IS_8251(com->pc98_if_type)) { #endif if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif sio_setreg(com, com_fifo, FIFO_XMT_RST | com->fifo_image); #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) for (rsa98_tmp = 0; rsa98_tmp < 2048; rsa98_tmp++) sio_setreg(com, com_fifo, FIFO_XMT_RST | com->fifo_image); } #endif com->obufs[0].l_queued = FALSE; com->obufs[1].l_queued = FALSE; if (com->state & CS_ODONE) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); com->tp->t_state &= ~TS_BUSY; } if (rw & FREAD) { #ifdef PC98 if (!IS_8251(com->pc98_if_type)) { if (com->pc98_if_type == COM_IF_RSA98III) for (rsa98_tmp = 0; rsa98_tmp < 2048; rsa98_tmp++) sio_getreg(com, com_data); #endif if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif sio_setreg(com, com_fifo, FIFO_RCV_RST | com->fifo_image); #ifdef PC98 } #endif com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } mtx_unlock_spin(&sio_lock); comstart(tp); } static int commctl(com, bits, how) struct com_s *com; int bits; int how; { int mcr; int msr; if (how == DMGET) { bits = TIOCM_LE; /* XXX - always enabled while open */ mcr = com->mcr_image; if (mcr & MCR_DTR) bits |= TIOCM_DTR; if (mcr & MCR_RTS) bits |= TIOCM_RTS; msr = com->prev_modem_status; if (msr & MSR_CTS) bits |= TIOCM_CTS; if (msr & MSR_DCD) bits |= TIOCM_CD; if (msr & MSR_DSR) bits |= TIOCM_DSR; /* * XXX - MSR_RI is naturally volatile, and we make MSR_TERI * more volatile by reading the modem status a lot. Perhaps * we should latch both bits until the status is read here. */ if (msr & (MSR_RI | MSR_TERI)) bits |= TIOCM_RI; return (bits); } mcr = 0; if (bits & TIOCM_DTR) mcr |= MCR_DTR; if (bits & TIOCM_RTS) mcr |= MCR_RTS; if (com->gone) return(0); mtx_lock_spin(&sio_lock); switch (how) { case DMSET: outb(com->modem_ctl_port, com->mcr_image = mcr | (com->mcr_image & MCR_IENABLE)); break; case DMBIS: outb(com->modem_ctl_port, com->mcr_image |= mcr); break; case DMBIC: outb(com->modem_ctl_port, com->mcr_image &= ~mcr); break; } mtx_unlock_spin(&sio_lock); return (0); } static void siosettimeout() { struct com_s *com; bool_t someopen; int unit; /* * Set our timeout period to 1 second if no polled devices are open. * Otherwise set it to max(1/200, 1/hz). * Enable timeouts iff some device is open. */ untimeout(comwakeup, (void *)NULL, sio_timeout_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < sio_numunits; ++unit) { com = com_addr(unit); if (com != NULL && com->tp != NULL && com->tp->t_state & TS_ISOPEN && !com->gone) { someopen = TRUE; if (com->poll || com->poll_output) { sio_timeout = hz > 200 ? hz / 200 : 1; break; } } } if (someopen) { sio_timeouts_until_log = hz / sio_timeout; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < sio_numunits; ++unit) { com = com_addr(unit); if (com != NULL && !com->gone && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { mtx_lock_spin(&sio_lock); siointr1(com); mtx_unlock_spin(&sio_lock); } } /* * Check for and log errors, but not too often. */ if (--sio_timeouts_until_log > 0) return; sio_timeouts_until_log = hz / sio_timeout; for (unit = 0; unit < sio_numunits; ++unit) { int errnum; com = com_addr(unit); if (com == NULL) continue; if (com->gone) continue; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { u_int delta; u_long total; mtx_lock_spin(&sio_lock); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; mtx_unlock_spin(&sio_lock); if (delta == 0) continue; total = com->error_counts[errnum] += delta; log(LOG_ERR, "sio%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); } } } #ifdef PC98 /* commint is called when modem control line changes */ static void commint(dev_t dev) { register struct tty *tp; int stat,delta; struct com_s *com; int mynor,unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); tp = com->tp; stat = com_tiocm_get(com); delta = com_tiocm_get_delta(com); if (com->state & CS_CTS_OFLOW) { if (stat & TIOCM_CTS) com->state |= CS_ODEVREADY; else com->state &= ~CS_ODEVREADY; } if ((delta & TIOCM_CAR) && (mynor & CALLOUT_MASK) == 0) { if (stat & TIOCM_CAR ) (void)ttyld_modem(tp, 1); else if (ttyld_modem(tp, 0) == 0) { /* negate DTR, RTS */ com_tiocm_bic(com, (tp->t_cflag & HUPCL) ? TIOCM_DTR|TIOCM_RTS|TIOCM_LE : TIOCM_LE ); /* disable IENABLE */ com_int_TxRx_disable( com ); } } } #endif -static void -disc_optim(tp, t, com) - struct tty *tp; - struct termios *t; - struct com_s *com; -{ - if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) - && (!(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; - com->hotchar = linesw[tp->t_line].l_hotchar; -} - /* * Following are all routines needed for SIO to act as console */ struct siocnstate { u_char dlbl; u_char dlbh; u_char ier; u_char cfcr; u_char mcr; }; /* * This is a function in order to not replicate "ttyd%d" more * places than absolutely necessary. */ static void siocnset(struct consdev *cd, int unit) { cd->cn_unit = unit; sprintf(cd->cn_name, "ttyd%d", unit); } #ifndef __alpha__ static speed_t siocngetspeed(Port_t, u_long rclk); #endif static void siocnclose(struct siocnstate *sp, Port_t iobase); static void siocnopen(struct siocnstate *sp, Port_t iobase, int speed); static void siocntxwait(Port_t iobase); #ifdef __alpha__ int siocnattach(int port, int speed); int siogdbattach(int port, int speed); int siogdbgetc(void); void siogdbputc(int c); #else static cn_probe_t siocnprobe; static cn_init_t siocninit; static cn_term_t siocnterm; #endif static cn_checkc_t siocncheckc; static cn_getc_t siocngetc; static cn_putc_t siocnputc; #ifndef __alpha__ CONS_DRIVER(sio, siocnprobe, siocninit, siocnterm, siocngetc, siocncheckc, siocnputc, NULL); #endif #if DDB > 0 static struct consdev gdbconsdev; #endif static void siocntxwait(iobase) Port_t iobase; { int timo; /* * Wait for any pending transmission to finish. Required to avoid * the UART lockup bug when the speed is changed, and for normal * transmits. */ timo = 100000; while ((inb(iobase + com_lsr) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY) && --timo != 0) ; } #ifndef __alpha__ /* * Read the serial port specified and try to figure out what speed * it's currently running at. We're assuming the serial port has * been initialized and is basicly idle. This routine is only intended * to be run at system startup. * * If the value read from the serial port doesn't make sense, return 0. */ static speed_t siocngetspeed(iobase, rclk) Port_t iobase; u_long rclk; { u_int divisor; u_char dlbh; u_char dlbl; u_char cfcr; cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); dlbl = inb(iobase + com_dlbl); dlbh = inb(iobase + com_dlbh); outb(iobase + com_cfcr, cfcr); divisor = dlbh << 8 | dlbl; /* XXX there should be more sanity checking. */ if (divisor == 0) return (CONSPEED); return (rclk / (16UL * divisor)); } #endif static void siocnopen(sp, iobase, speed) struct siocnstate *sp; Port_t iobase; int speed; { u_int divisor; u_char dlbh; u_char dlbl; /* * Save all the device control registers except the fifo register * and set our default ones (cs8 -parenb speed=comdefaultrate). * We can't save the fifo register since it is read-only. */ sp->ier = inb(iobase + com_ier); outb(iobase + com_ier, 0); /* spltty() doesn't stop siointr() */ siocntxwait(iobase); sp->cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); sp->dlbl = inb(iobase + com_dlbl); sp->dlbh = inb(iobase + com_dlbh); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (Startech), setting them clears the * data input register. This also reduces the effects of the * UMC8669F bug. */ divisor = siodivisor(comdefaultrclk, speed); dlbl = divisor & 0xFF; if (sp->dlbl != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = divisor >> 8; if (sp->dlbh != dlbh) outb(iobase + com_dlbh, dlbh); outb(iobase + com_cfcr, CFCR_8BITS); sp->mcr = inb(iobase + com_mcr); /* * We don't want interrupts, but must be careful not to "disable" * them by clearing the MCR_IENABLE bit, since that might cause * an interrupt by floating the IRQ line. */ outb(iobase + com_mcr, (sp->mcr & MCR_IENABLE) | MCR_DTR | MCR_RTS); } static void siocnclose(sp, iobase) struct siocnstate *sp; Port_t iobase; { /* * Restore the device control registers. */ siocntxwait(iobase); outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); if (sp->dlbl != inb(iobase + com_dlbl)) outb(iobase + com_dlbl, sp->dlbl); if (sp->dlbh != inb(iobase + com_dlbh)) outb(iobase + com_dlbh, sp->dlbh); outb(iobase + com_cfcr, sp->cfcr); /* * XXX damp oscillations of MCR_DTR and MCR_RTS by not restoring them. */ outb(iobase + com_mcr, sp->mcr | MCR_DTR | MCR_RTS); outb(iobase + com_ier, sp->ier); } #ifndef __alpha__ static void siocnprobe(cp) struct consdev *cp; { speed_t boot_speed; u_char cfcr; u_int divisor; int s, unit; struct siocnstate sp; /* * Find our first enabled console, if any. If it is a high-level * console device, then initialize it and return successfully. * If it is a low-level console device, then initialize it and * return unsuccessfully. It must be initialized in both cases * for early use by console drivers and debuggers. Initializing * the hardware is not necessary in all cases, since the i/o * routines initialize it on the fly, but it is necessary if * input might arrive while the hardware is switched back to an * uninitialized state. We can't handle multiple console devices * yet because our low-level routines don't take a device arg. * We trust the user to set the console flags properly so that we * don't need to probe. */ cp->cn_pri = CN_DEAD; for (unit = 0; unit < 16; unit++) { /* XXX need to know how many */ int flags; if (resource_disabled("sio", unit)) continue; if (resource_int_value("sio", unit, "flags", &flags)) continue; if (COM_CONSOLE(flags) || COM_DEBUGGER(flags)) { int port; Port_t iobase; if (resource_int_value("sio", unit, "port", &port)) continue; iobase = port; s = spltty(); if (boothowto & RB_SERIAL) { boot_speed = siocngetspeed(iobase, comdefaultrclk); if (boot_speed) comdefaultrate = boot_speed; } /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); divisor = siodivisor(comdefaultrclk, comdefaultrate); outb(iobase + com_dlbl, divisor & 0xff); outb(iobase + com_dlbh, divisor >> 8); outb(iobase + com_cfcr, cfcr); siocnopen(&sp, iobase, comdefaultrate); splx(s); if (COM_CONSOLE(flags) && !COM_LLCONSOLE(flags)) { siocnset(cp, unit); cp->cn_pri = COM_FORCECONSOLE(flags) || boothowto & RB_SERIAL ? CN_REMOTE : CN_NORMAL; siocniobase = iobase; siocnunit = unit; } if (COM_DEBUGGER(flags)) { printf("sio%d: gdb debugging port\n", unit); siogdbiobase = iobase; siogdbunit = unit; #if DDB > 0 siocnset(&gdbconsdev, unit); gdb_arg = &gdbconsdev; gdb_getc = siocngetc; gdb_putc = siocnputc; #endif } } } #ifdef __i386__ #if DDB > 0 /* * XXX Ugly Compatability. * If no gdb port has been specified, set it to be the console * as some configuration files don't specify the gdb port. */ if (gdb_arg == NULL && (boothowto & RB_GDB)) { printf("Warning: no GDB port specified. Defaulting to sio%d.\n", siocnunit); printf("Set flag 0x80 on desired GDB port in your\n"); printf("configuration file (currently sio only).\n"); siogdbiobase = siocniobase; siogdbunit = siocnunit; siocnset(&gdbconsdev, siocnunit); gdb_arg = &gdbconsdev; gdb_getc = siocngetc; gdb_putc = siocnputc; } #endif #endif } static void siocninit(cp) struct consdev *cp; { comconsole = cp->cn_unit; } static void siocnterm(cp) struct consdev *cp; { comconsole = -1; } #endif #ifdef __alpha__ CONS_DRIVER(sio, NULL, NULL, NULL, siocngetc, siocncheckc, siocnputc, NULL); int siocnattach(port, speed) int port; int speed; { int s; u_char cfcr; u_int divisor; struct siocnstate sp; int unit = 0; /* XXX random value! */ siocniobase = port; siocnunit = unit; comdefaultrate = speed; sio_consdev.cn_pri = CN_NORMAL; siocnset(&sio_consdev, unit); s = spltty(); /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(siocniobase + com_cfcr); outb(siocniobase + com_cfcr, CFCR_DLAB | cfcr); divisor = siodivisor(comdefaultrclk, comdefaultrate); outb(siocniobase + com_dlbl, divisor & 0xff); outb(siocniobase + com_dlbh, divisor >> 8); outb(siocniobase + com_cfcr, cfcr); siocnopen(&sp, siocniobase, comdefaultrate); splx(s); cnadd(&sio_consdev); return (0); } int siogdbattach(port, speed) int port; int speed; { int s; u_char cfcr; u_int divisor; struct siocnstate sp; int unit = 1; /* XXX random value! */ siogdbiobase = port; gdbdefaultrate = speed; printf("sio%d: gdb debugging port\n", unit); siogdbunit = unit; #if DDB > 0 siocnset(&gdbconsdev, unit); gdb_arg = &gdbconsdev; gdb_getc = siocngetc; gdb_putc = siocnputc; #endif s = spltty(); /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(siogdbiobase + com_cfcr); outb(siogdbiobase + com_cfcr, CFCR_DLAB | cfcr); divisor = siodivisor(comdefaultrclk, gdbdefaultrate); outb(siogdbiobase + com_dlbl, divisor & 0xff); outb(siogdbiobase + com_dlbh, divisor >> 8); outb(siogdbiobase + com_cfcr, cfcr); siocnopen(&sp, siogdbiobase, gdbdefaultrate); splx(s); return (0); } #endif static int siocncheckc(struct consdev *cd) { int c; Port_t iobase; int s; struct siocnstate sp; speed_t speed; if (cd->cn_unit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); if (inb(iobase + com_lsr) & LSR_RXRDY) c = inb(iobase + com_data); else c = -1; siocnclose(&sp, iobase); splx(s); return (c); } static int siocngetc(struct consdev *cd) { int c; Port_t iobase; int s; struct siocnstate sp; speed_t speed; if (cd->cn_unit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); while (!(inb(iobase + com_lsr) & LSR_RXRDY)) ; c = inb(iobase + com_data); siocnclose(&sp, iobase); splx(s); return (c); } static void siocnputc(struct consdev *cd, int c) { int need_unlock; int s; struct siocnstate sp; Port_t iobase; speed_t speed; if (cd->cn_unit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); need_unlock = 0; if (sio_inited == 2 && !mtx_owned(&sio_lock)) { mtx_lock_spin(&sio_lock); need_unlock = 1; } siocnopen(&sp, iobase, speed); siocntxwait(iobase); outb(iobase + com_data, c); siocnclose(&sp, iobase); if (need_unlock) mtx_unlock_spin(&sio_lock); splx(s); } #ifdef __alpha__ int siogdbgetc() { int c; Port_t iobase; speed_t speed; int s; struct siocnstate sp; if (siogdbunit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); while (!(inb(iobase + com_lsr) & LSR_RXRDY)) ; c = inb(iobase + com_data); siocnclose(&sp, iobase); splx(s); return (c); } void siogdbputc(c) int c; { Port_t iobase; speed_t speed; int s; struct siocnstate sp; if (siogdbunit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); siocntxwait(siogdbiobase); outb(siogdbiobase + com_data, c); siocnclose(&sp, siogdbiobase); splx(s); } #endif #ifdef PC98 /* * pc98 local function */ static void com_tiocm_set(struct com_s *com, int msr) { int s; int tmp = 0; int mask = CMD8251_TxEN|CMD8251_RxEN|CMD8251_DTR|CMD8251_RTS; s=spltty(); com->pc98_prev_modem_status = ( msr & (TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ) | ( com->pc98_prev_modem_status & ~(TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ); tmp |= (CMD8251_TxEN|CMD8251_RxEN); if ( msr & TIOCM_DTR ) tmp |= CMD8251_DTR; if ( msr & TIOCM_RTS ) tmp |= CMD8251_RTS; pc98_i8251_clear_or_cmd( com, mask, tmp ); splx(s); } static void com_tiocm_bis(struct com_s *com, int msr) { int s; int tmp = 0; s=spltty(); com->pc98_prev_modem_status |= ( msr & (TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ); tmp |= CMD8251_TxEN|CMD8251_RxEN; if ( msr & TIOCM_DTR ) tmp |= CMD8251_DTR; if ( msr & TIOCM_RTS ) tmp |= CMD8251_RTS; pc98_i8251_or_cmd( com, tmp ); splx(s); } static void com_tiocm_bic(struct com_s *com, int msr) { int s; int tmp = msr; s=spltty(); com->pc98_prev_modem_status &= ~( msr & (TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ); if ( msr & TIOCM_DTR ) tmp |= CMD8251_DTR; if ( msr & TIOCM_RTS ) tmp |= CMD8251_RTS; pc98_i8251_clear_cmd( com, tmp ); splx(s); } static int com_tiocm_get(struct com_s *com) { return( com->pc98_prev_modem_status ); } static int com_tiocm_get_delta(struct com_s *com) { int tmp; tmp = com->pc98_modem_delta; com->pc98_modem_delta = 0; return( tmp ); } /* convert to TIOCM_?? ( ioctl.h ) */ static int pc98_get_modem_status(struct com_s *com) { register int msr; msr = com->pc98_prev_modem_status & ~(TIOCM_CAR|TIOCM_RI|TIOCM_DSR|TIOCM_CTS); if (com->pc98_8251fifo_enable) { int stat2; stat2 = inb(I8251F_msr); if ( stat2 & CICSCDF_CD ) msr |= TIOCM_CAR; if ( stat2 & CICSCDF_CI ) msr |= TIOCM_RI; if ( stat2 & CICSCDF_DR ) msr |= TIOCM_DSR; if ( stat2 & CICSCDF_CS ) msr |= TIOCM_CTS; #if COM_CARRIER_DETECT_EMULATE if ( msr & (TIOCM_DSR|TIOCM_CTS) ) { msr |= TIOCM_CAR; } #endif } else { int stat, stat2; stat = inb(com->sts_port); stat2 = inb(com->in_modem_port); if ( !(stat2 & CICSCD_CD) ) msr |= TIOCM_CAR; if ( !(stat2 & CICSCD_CI) ) msr |= TIOCM_RI; if ( stat & STS8251_DSR ) msr |= TIOCM_DSR; if ( !(stat2 & CICSCD_CS) ) msr |= TIOCM_CTS; #if COM_CARRIER_DETECT_EMULATE if ( msr & (TIOCM_DSR|TIOCM_CTS) ) { msr |= TIOCM_CAR; } #endif } return(msr); } static void pc98_check_msr(void* chan) { int msr, delta; int s; register struct tty *tp; struct com_s *com; int mynor; int unit; dev_t dev; dev=(dev_t)chan; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); tp = com->tp; s = spltty(); msr = pc98_get_modem_status(com); /* make change flag */ delta = msr ^ com->pc98_prev_modem_status; if ( delta & TIOCM_CAR ) { if ( com->modem_car_chg_timer ) { if ( -- com->modem_car_chg_timer ) msr ^= TIOCM_CAR; } else { if ((com->modem_car_chg_timer = (msr & TIOCM_CAR) ? DCD_ON_RECOGNITION : DCD_OFF_TOLERANCE) != 0) msr ^= TIOCM_CAR; } } else com->modem_car_chg_timer = 0; delta = ( msr ^ com->pc98_prev_modem_status ) & (TIOCM_CAR|TIOCM_RI|TIOCM_DSR|TIOCM_CTS); com->pc98_prev_modem_status = msr; delta = ( com->pc98_modem_delta |= delta ); splx(s); if ( com->modem_checking || (tp->t_state & (TS_ISOPEN)) ) { if ( delta ) { commint(dev); } timeout(pc98_check_msr, (caddr_t)dev, PC98_CHECK_MODEM_INTERVAL); } else { com->modem_checking = 0; } } static void pc98_msrint_start(dev_t dev) { struct com_s *com; int mynor; int unit; int s = spltty(); mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); /* modem control line check routine envoke interval is 1/10 sec */ if ( com->modem_checking == 0 ) { com->pc98_prev_modem_status = pc98_get_modem_status(com); com->pc98_modem_delta = 0; timeout(pc98_check_msr, (caddr_t)dev, PC98_CHECK_MODEM_INTERVAL); com->modem_checking = 1; } splx(s); } static void pc98_disable_i8251_interrupt(struct com_s *com, int mod) { /* disable interrupt */ register int tmp; mod |= ~(IEN_Tx|IEN_TxEMP|IEN_Rx); COM_INT_DISABLE tmp = inb( com->intr_ctrl_port ) & ~(IEN_Tx|IEN_TxEMP|IEN_Rx); outb( com->intr_ctrl_port, (com->intr_enable&=~mod) | tmp ); COM_INT_ENABLE } static void pc98_enable_i8251_interrupt(struct com_s *com, int mod) { register int tmp; COM_INT_DISABLE tmp = inb( com->intr_ctrl_port ) & ~(IEN_Tx|IEN_TxEMP|IEN_Rx); outb( com->intr_ctrl_port, (com->intr_enable|=mod) | tmp ); COM_INT_ENABLE } static int pc98_check_i8251_interrupt(struct com_s *com) { return ( com->intr_enable & 0x07 ); } static void pc98_i8251_clear_cmd(struct com_s *com, int x) { int tmp; COM_INT_DISABLE tmp = com->pc98_prev_siocmd & ~(x); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, 0); outb(com->cmd_port, tmp); com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_ENABLE); COM_INT_ENABLE } static void pc98_i8251_or_cmd(struct com_s *com, int x) { int tmp; COM_INT_DISABLE if (com->pc98_8251fifo_enable) outb(I8251F_fcr, 0); tmp = com->pc98_prev_siocmd | (x); outb(com->cmd_port, tmp); com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_ENABLE); COM_INT_ENABLE } static void pc98_i8251_set_cmd(struct com_s *com, int x) { int tmp; COM_INT_DISABLE if (com->pc98_8251fifo_enable) outb(I8251F_fcr, 0); tmp = (x); outb(com->cmd_port, tmp); com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_ENABLE); COM_INT_ENABLE } static void pc98_i8251_clear_or_cmd(struct com_s *com, int clr, int x) { int tmp; COM_INT_DISABLE if (com->pc98_8251fifo_enable) outb(I8251F_fcr, 0); tmp = com->pc98_prev_siocmd & ~(clr); tmp |= (x); outb(com->cmd_port, tmp); com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_ENABLE); COM_INT_ENABLE } static int pc98_i8251_get_cmd(struct com_s *com) { return com->pc98_prev_siocmd; } static int pc98_i8251_get_mod(struct com_s *com) { return com->pc98_prev_siomod; } static void pc98_i8251_reset(struct com_s *com, int mode, int command) { if (com->pc98_8251fifo_enable) outb(I8251F_fcr, 0); outb(com->cmd_port, 0); /* dummy */ DELAY(2); outb(com->cmd_port, 0); /* dummy */ DELAY(2); outb(com->cmd_port, 0); /* dummy */ DELAY(2); outb(com->cmd_port, CMD8251_RESET); /* internal reset */ DELAY(2); outb(com->cmd_port, mode ); /* mode register */ com->pc98_prev_siomod = mode; DELAY(2); pc98_i8251_set_cmd( com, (command|CMD8251_ER) ); DELAY(10); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_ENABLE | CTRL8251F_XMT_RST | CTRL8251F_RCV_RST); } static void pc98_check_sysclock(void) { /* get system clock from port */ if ( pc98_machine_type & M_8M ) { /* 8 MHz system & H98 */ sysclock = 8; } else { /* 5 MHz system */ sysclock = 5; } } static void com_cflag_and_speed_set( struct com_s *com, int cflag, int speed) { int cfcr=0; int previnterrupt; u_int count; if (pc98_ttspeedtab(com, speed, &count) != 0) return; previnterrupt = pc98_check_i8251_interrupt(com); pc98_disable_i8251_interrupt( com, IEN_Tx|IEN_TxEMP|IEN_Rx ); switch ( cflag&CSIZE ) { case CS5: cfcr = MOD8251_5BITS; break; case CS6: cfcr = MOD8251_6BITS; break; case CS7: cfcr = MOD8251_7BITS; break; case CS8: cfcr = MOD8251_8BITS; break; } if ( cflag&PARENB ) { if ( cflag&PARODD ) cfcr |= MOD8251_PODD; else cfcr |= MOD8251_PEVEN; } else cfcr |= MOD8251_PDISAB; if ( cflag&CSTOPB ) cfcr |= MOD8251_STOP2; else cfcr |= MOD8251_STOP1; if ( count & 0x10000 ) cfcr |= MOD8251_CLKX1; else cfcr |= MOD8251_CLKX16; if (epson_machine_id != 0x20) { /* XXX */ int tmp; while (!((tmp = inb(com->sts_port)) & STS8251_TxEMP)) ; } /* set baud rate from ospeed */ pc98_set_baud_rate( com, count ); if ( cfcr != pc98_i8251_get_mod(com) ) pc98_i8251_reset(com, cfcr, pc98_i8251_get_cmd(com) ); pc98_enable_i8251_interrupt( com, previnterrupt ); } static int pc98_ttspeedtab(struct com_s *com, int speed, u_int *divisor) { int if_type, effect_sp, count = -1, mod; if_type = com->pc98_if_type & 0x0f; switch (com->pc98_if_type) { case COM_IF_INTERNAL: if (PC98SIO_baud_rate_port(if_type) != -1) { count = ttspeedtab(speed, if_8251_type[if_type].speedtab); if (count > 0) { count |= COM1_EXT_CLOCK; break; } } /* for *1CLK asynchronous! mode, TEFUTEFU */ mod = (sysclock == 5) ? 2457600 : 1996800; effect_sp = ttspeedtab( speed, pc98speedtab ); if ( effect_sp < 0 ) /* XXX */ effect_sp = ttspeedtab( (speed - 1), pc98speedtab ); if ( effect_sp <= 0 ) return effect_sp; if ( effect_sp == speed ) mod /= 16; if ( mod % effect_sp ) return(-1); count = mod / effect_sp; if ( count > 65535 ) return(-1); if ( effect_sp != speed ) count |= 0x10000; break; case COM_IF_PC9861K_1: case COM_IF_PC9861K_2: count = 1; break; case COM_IF_IND_SS_1: case COM_IF_IND_SS_2: case COM_IF_PIO9032B_1: case COM_IF_PIO9032B_2: count = ttspeedtab( speed, if_8251_type[if_type].speedtab ); break; case COM_IF_B98_01_1: case COM_IF_B98_01_2: count = ttspeedtab( speed, if_8251_type[if_type].speedtab ); #ifdef B98_01_OLD if (count == 0 || count == 1) { count += 4; count |= 0x20000; /* x1 mode for 76800 and 153600 */ } #endif break; } if (count < 0) return count; *divisor = (u_int) count; return 0; } static void pc98_set_baud_rate( struct com_s *com, u_int count ) { int if_type, io, s; if_type = com->pc98_if_type & 0x0f; io = rman_get_start(com->ioportres) & 0xff00; switch (com->pc98_if_type) { case COM_IF_INTERNAL: if (PC98SIO_baud_rate_port(if_type) != -1) { if (count & COM1_EXT_CLOCK) { outb((Port_t)PC98SIO_baud_rate_port(if_type), count & 0xff); break; } else { outb((Port_t)PC98SIO_baud_rate_port(if_type), 0x09); } } if (count == 0) return; /* set i8253 */ s = splclock(); if (count != 3) outb( 0x77, 0xb6 ); else outb( 0x77, 0xb4 ); outb( 0x5f, 0); outb( 0x75, count & 0xff ); outb( 0x5f, 0); outb( 0x75, (count >> 8) & 0xff ); splx(s); break; case COM_IF_IND_SS_1: case COM_IF_IND_SS_2: outb(io | PC98SIO_intr_ctrl_port(if_type), 0); outb(io | PC98SIO_baud_rate_port(if_type), 0); outb(io | PC98SIO_baud_rate_port(if_type), 0xc0); outb(io | PC98SIO_baud_rate_port(if_type), (count >> 8) | 0x80); outb(io | PC98SIO_baud_rate_port(if_type), count & 0xff); break; case COM_IF_PIO9032B_1: case COM_IF_PIO9032B_2: outb(io | PC98SIO_baud_rate_port(if_type), count); break; case COM_IF_B98_01_1: case COM_IF_B98_01_2: outb(io | PC98SIO_baud_rate_port(if_type), count & 0x0f); #ifdef B98_01_OLD /* * Some old B98_01 board should be controlled * in different way, but this hasn't been tested yet. */ outb(io | PC98SIO_func_port(if_type), (count & 0x20000) ? 0xf0 : 0xf2); #endif break; } } static int pc98_check_if_type(device_t dev, struct siodev *iod) { int irr, io, if_type, tmp; static short irq_tab[2][8] = { { 3, 5, 6, 9, 10, 12, 13, -1}, { 3, 10, 12, 13, 5, 6, 9, -1} }; if_type = iod->if_type & 0x0f; iod->irq = 0; io = isa_get_port(dev) & 0xff00; if (IS_8251(iod->if_type)) { if (PC98SIO_func_port(if_type) != -1) { outb(io | PC98SIO_func_port(if_type), 0xf2); tmp = ttspeedtab(9600, if_8251_type[if_type].speedtab); if (tmp != -1 && PC98SIO_baud_rate_port(if_type) != -1) outb(io | PC98SIO_baud_rate_port(if_type), tmp); } iod->cmd = io | PC98SIO_cmd_port(if_type); iod->sts = io | PC98SIO_sts_port(if_type); iod->mod = io | PC98SIO_in_modem_port(if_type); iod->ctrl = io | PC98SIO_intr_ctrl_port(if_type); if (iod->if_type == COM_IF_INTERNAL) { iod->irq = 4; if (pc98_check_8251vfast()) { PC98SIO_baud_rate_port(if_type) = I8251F_div; if_8251_type[if_type].speedtab = pc98fast_speedtab; } } else { tmp = inb( iod->mod ) & if_8251_type[if_type].irr_mask; if ((isa_get_port(dev) & 0xff) == IO_COM2) iod->irq = irq_tab[0][tmp]; else iod->irq = irq_tab[1][tmp]; } } else { irr = if_16550a_type[if_type].irr_read; #ifdef COM_MULTIPORT if (!COM_ISMULTIPORT(device_get_flags(dev)) || device_get_unit(dev) == COM_MPMASTER(device_get_flags(dev))) #endif if (irr != -1) { tmp = inb(io | irr); if (isa_get_port(dev) & 0x01) /* XXX depend on RSB-384 */ iod->irq = irq_tab[1][tmp >> 3]; else iod->irq = irq_tab[0][tmp & 0x07]; } } if ( iod->irq == -1 ) return -1; return 0; } static void pc98_set_ioport(struct com_s *com) { int if_type = com->pc98_if_type & 0x0f; Port_t io = rman_get_start(com->ioportres) & 0xff00; pc98_check_sysclock(); com->data_port = io | PC98SIO_data_port(if_type); com->cmd_port = io | PC98SIO_cmd_port(if_type); com->sts_port = io | PC98SIO_sts_port(if_type); com->in_modem_port = io | PC98SIO_in_modem_port(if_type); com->intr_ctrl_port = io | PC98SIO_intr_ctrl_port(if_type); } static int pc98_check_8251vfast(void) { int i; outb(I8251F_div, 0x8c); DELAY(10); for (i = 0; i < 100; i++) { if ((inb(I8251F_div) & 0x80) != 0) { i = 0; break; } DELAY(1); } outb(I8251F_div, 0); DELAY(10); for (; i < 100; i++) { if ((inb(I8251F_div) & 0x80) == 0) return 1; DELAY(1); } return 0; } static int pc98_check_8251fifo(void) { u_char tmp1, tmp2; tmp1 = inb(I8251F_iir); DELAY(10); tmp2 = inb(I8251F_iir); if (((tmp1 ^ tmp2) & 0x40) != 0 && ((tmp1 | tmp2) & 0x20) == 0) return 1; return 0; } #endif /* PC98 defined */ diff --git a/sys/pc98/pc98/sio.c b/sys/pc98/pc98/sio.c index e2c652f1d47f..12c824371a20 100644 --- a/sys/pc98/pc98/sio.c +++ b/sys/pc98/pc98/sio.c @@ -1,5063 +1,5043 @@ /*- * Copyright (c) 1991 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. * 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$ * from: @(#)com.c 7.5 (Berkeley) 5/16/91 * from: i386/isa sio.c,v 1.234 */ #include "opt_comconsole.h" #include "opt_compat.h" #include "opt_ddb.h" #include "opt_sio.h" /* * Serial driver, based on 386BSD-0.1 com driver. * Mostly rewritten to use pseudo-DMA. * Works for National Semiconductor NS8250-NS16550AF UARTs. * COM driver, based on HP dca driver. * * Changes for PC-Card integration: * - Added PC-Card driver table and handlers */ /*=============================================================== * 386BSD(98),FreeBSD-1.1x(98) com driver. * ----- * modified for PC9801 by M.Ishii * Kyoto University Microcomputer Club (KMC) * Chou "TEFUTEFU" Hirotomi * Kyoto Univ. the faculty of medicine *=============================================================== * FreeBSD-2.0.1(98) sio driver. * ----- * modified for pc98 Internal i8251 and MICRO CORE MC16550II * T.Koike(hfc01340@niftyserve.or.jp) * implement kernel device configuration * aizu@orient.center.nitech.ac.jp * * Notes. * ----- * PC98 localization based on 386BSD(98) com driver. Using its PC98 local * functions. * This driver is under debugging,has bugs. */ /* * modified for AIWA B98-01 * by T.Hatanou last update: 15 Sep.1995 */ /* * Modified by Y.Takahashi of Kogakuin University. */ /* * modified for 8251(FIFO) by Seigo TANIMURA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if DDB > 0 #include #endif #include #include #include #include #ifdef PC98 #include #include #endif #ifdef COM_ESP #include #endif #include #ifdef PC98 #include #include #endif #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) #define MINOR_TO_UNIT(mynor) ((((mynor) & ~0xffffU) >> (8 + 3)) \ | ((mynor) & 0x1f)) #define UNIT_TO_MINOR(unit) ((((unit) & ~0x1fU) << (8 + 3)) \ | ((unit) & 0x1f)) /* * Meaning of flags: * * 0x00000001 shared IRQs * 0x00000002 disable FIFO * 0x00000008 recover sooner from lost output interrupts * 0x00000010 device is potential system console * 0x00000020 device is forced to become system console * 0x00000040 device is reserved for low-level IO * 0x00000080 use this port for remote kernel debugging * 0x0000??00 minor number of master port * 0x00010000 PPS timestamping on CTS instead of DCD * 0x00080000 IIR_TXRDY bug * 0x00400000 If no comconsole found then mark as a comconsole * 0x1?000000 interface type */ #ifdef COM_MULTIPORT /* checks in flags for multiport and which is multiport "master chip" * for a given card */ #define COM_ISMULTIPORT(flags) ((flags) & 0x01) #define COM_MPMASTER(flags) (((flags) >> 8) & 0x0ff) #ifndef PC98 #define COM_NOTAST4(flags) ((flags) & 0x04) #endif #else #define COM_ISMULTIPORT(flags) (0) #endif /* COM_MULTIPORT */ #define COM_C_IIR_TXRDYBUG 0x80000 #define COM_CONSOLE(flags) ((flags) & 0x10) #define COM_DEBUGGER(flags) ((flags) & 0x80) #ifndef PC98 #define COM_FIFOSIZE(flags) (((flags) & 0xff000000) >> 24) #endif #define COM_FORCECONSOLE(flags) ((flags) & 0x20) #define COM_IIR_TXRDYBUG(flags) ((flags) & COM_C_IIR_TXRDYBUG) #define COM_LLCONSOLE(flags) ((flags) & 0x40) #define COM_LOSESOUTINTS(flags) ((flags) & 0x08) #define COM_NOFIFO(flags) ((flags) & 0x02) #ifndef PC98 #define COM_NOSCR(flags) ((flags) & 0x100000) #endif #define COM_PPSCTS(flags) ((flags) & 0x10000) #ifndef PC98 #define COM_ST16650A(flags) ((flags) & 0x20000) #define COM_TI16754(flags) ((flags) & 0x200000) #endif #define sio_getreg(com, off) \ (bus_space_read_1((com)->bst, (com)->bsh, (off))) #define sio_setreg(com, off, value) \ (bus_space_write_1((com)->bst, (com)->bsh, (off), (value))) /* * com state bits. * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher * than the other bits so that they can be tested as a group without masking * off the low bits. * * The following com and tty flags correspond closely: * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and * comstop()) * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ #define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ #define CSE_BUSYCHECK 1 /* siobusycheck() scheduled */ static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 "interrupt-level buffer overflow", #define CE_TTY_BUF_OVERFLOW 2 "tty-level buffer overflow", }; #define CE_NTYPES 3 #define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) /* types. XXX - should be elsewhere */ typedef u_int Port_t; /* hardware port */ typedef u_char bool_t; /* boolean */ /* queue of linear buffers */ struct lbq { u_char *l_head; /* next char to process */ u_char *l_tail; /* one past the last char to process */ struct lbq *l_next; /* next in queue */ bool_t l_queued; /* nonzero if queued */ }; /* com device structure */ struct com_s { u_char state; /* miscellaneous flag bits */ bool_t active_out; /* nonzero if the callout device is open */ u_char cfcr_image; /* copy of value written to CFCR */ #ifdef COM_ESP bool_t esp; /* is this unit a hayes esp board? */ #endif u_char extra_state; /* more flag bits, separate for order trick */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t loses_outints; /* nonzero if device loses output interrupts */ u_char mcr_image; /* copy of value written to MCR */ #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ bool_t no_irq; /* nonzero if irq is not attached */ bool_t gone; /* hardware disappeared */ bool_t poll; /* nonzero if polling is required */ bool_t poll_output; /* nonzero if polling for output is required */ bool_t st16650a; /* nonzero if Startech 16650A compatible */ int unit; /* unit number */ int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ u_int flags; /* copy of device flags */ u_int tx_fifo_size; u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly * because there would be too many side effects to handle conveniently. * Instead, it reads copies of the registers stored here by the * interrupt handler. */ u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ibufold; /* old input buffer, to be freed */ u_char *ihighwater; /* threshold in input buffer */ u_char *iptr; /* next free spot in input buffer */ int ibufsize; /* size of ibuf (not include error bytes) */ int ierroff; /* offset of error bytes in ibuf */ struct lbq obufq; /* head of queue of output buffers */ struct lbq obufs[2]; /* output buffers */ bus_space_tag_t bst; bus_space_handle_t bsh; #ifdef PC98 Port_t cmd_port; Port_t sts_port; Port_t in_modem_port; Port_t intr_ctrl_port; Port_t rsabase; /* Iobase address of an I/O-DATA RSA board. */ int intr_enable; int pc98_prev_modem_status; int pc98_modem_delta; int modem_car_chg_timer; int pc98_prev_siocmd; int pc98_prev_siomod; int modem_checking; int pc98_if_type; bool_t pc98_8251fifo; bool_t pc98_8251fifo_enable; #endif /* PC98 */ Port_t data_port; /* i/o ports */ #ifdef COM_ESP Port_t esp_port; #endif Port_t int_ctl_port; Port_t int_id_port; Port_t modem_ctl_port; Port_t line_status_port; Port_t modem_status_port; struct tty *tp; /* cross reference */ /* Initial state. */ struct termios it_in; /* should be in struct tty */ struct termios it_out; /* Lock state. */ struct termios lt_in; /* should be in struct tty */ struct termios lt_out; bool_t do_timestamp; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_timestamp; struct pps_state pps; int pps_bit; #ifdef ALT_BREAK_TO_DEBUGGER int alt_brk_state; #endif u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; u_long error_counts[CE_NTYPES]; u_long rclk; struct resource *irqres; struct resource *ioportres; int ioportrid; void *cookie; dev_t devs[6]; /* * Data area for output buffers. Someday we should build the output * buffer queue without copying data. */ #ifdef PC98 int obufsize; u_char *obuf1; u_char *obuf2; #else u_char obuf1[256]; u_char obuf2[256]; #endif }; #ifdef COM_ESP static int espattach(struct com_s *com, Port_t esp_port); #endif static timeout_t siobusycheck; static u_int siodivisor(u_long rclk, speed_t speed); static timeout_t siodtrwakeup; static void comhardclose(struct com_s *com); static void sioinput(struct com_s *com); static void siointr1(struct com_s *com); static void siointr(void *arg); static int commctl(struct com_s *com, int bits, int how); static int comparam(struct tty *tp, struct termios *t); static void siopoll(void *); static void siosettimeout(void); static int siosetwater(struct com_s *com, speed_t speed); static void comstart(struct tty *tp); static void comstop(struct tty *tp, int rw); static timeout_t comwakeup; -static void disc_optim(struct tty *tp, struct termios *t, - struct com_s *com); char sio_driver_name[] = "sio"; static struct mtx sio_lock; static int sio_inited; /* table and macro for fast conversion from a unit number to its com struct */ devclass_t sio_devclass; #define com_addr(unit) ((struct com_s *) \ devclass_get_softc(sio_devclass, unit)) /* XXX */ static d_open_t sioopen; static d_close_t sioclose; static d_read_t sioread; static d_write_t siowrite; static d_ioctl_t sioioctl; static struct cdevsw sio_cdevsw = { .d_version = D_VERSION, .d_open = sioopen, .d_close = sioclose, .d_read = sioread, .d_write = siowrite, .d_ioctl = sioioctl, .d_name = sio_driver_name, .d_flags = D_TTY | D_NEEDGIANT, }; int comconsole = -1; static volatile speed_t comdefaultrate = CONSPEED; static u_long comdefaultrclk = DEFAULT_RCLK; SYSCTL_ULONG(_machdep, OID_AUTO, conrclk, CTLFLAG_RW, &comdefaultrclk, 0, ""); static speed_t gdbdefaultrate = GDBSPEED; SYSCTL_UINT(_machdep, OID_AUTO, gdbspeed, CTLFLAG_RW, &gdbdefaultrate, GDBSPEED, ""); static u_int com_events; /* input chars + weighted output completions */ static Port_t siocniobase; static int siocnunit = -1; static Port_t siogdbiobase; static int siogdbunit = -1; static void *sio_slow_ih; static void *sio_fast_ih; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); static int sio_numunits; #ifdef PC98 struct siodev { short if_type; short irq; Port_t cmd, sts, ctrl, mod; }; static int sysclock; #define COM_INT_DISABLE {int previpri; previpri=spltty(); #define COM_INT_ENABLE splx(previpri);} #define IEN_TxFLAG IEN_Tx #define COM_CARRIER_DETECT_EMULATE 0 #define PC98_CHECK_MODEM_INTERVAL (hz/10) #define DCD_OFF_TOLERANCE 2 #define DCD_ON_RECOGNITION 2 #define IS_8251(if_type) (!(if_type & 0x10)) #define COM1_EXT_CLOCK 0x40000 static void commint(dev_t dev); static void com_tiocm_set(struct com_s *com, int msr); static void com_tiocm_bis(struct com_s *com, int msr); static void com_tiocm_bic(struct com_s *com, int msr); static int com_tiocm_get(struct com_s *com); static int com_tiocm_get_delta(struct com_s *com); static void pc98_msrint_start(dev_t dev); static void com_cflag_and_speed_set(struct com_s *com, int cflag, int speed); static int pc98_ttspeedtab(struct com_s *com, int speed, u_int *divisor); static int pc98_get_modem_status(struct com_s *com); static timeout_t pc98_check_msr; static void pc98_set_baud_rate(struct com_s *com, u_int count); static void pc98_i8251_reset(struct com_s *com, int mode, int command); static void pc98_disable_i8251_interrupt(struct com_s *com, int mod); static void pc98_enable_i8251_interrupt(struct com_s *com, int mod); static int pc98_check_i8251_interrupt(struct com_s *com); static int pc98_i8251_get_cmd(struct com_s *com); static int pc98_i8251_get_mod(struct com_s *com); static void pc98_i8251_set_cmd(struct com_s *com, int x); static void pc98_i8251_or_cmd(struct com_s *com, int x); static void pc98_i8251_clear_cmd(struct com_s *com, int x); static void pc98_i8251_clear_or_cmd(struct com_s *com, int clr, int x); static int pc98_check_if_type(device_t dev, struct siodev *iod); static int pc98_check_8251vfast(void); static int pc98_check_8251fifo(void); static void pc98_check_sysclock(void); static void pc98_set_ioport(struct com_s *com); #define com_int_Tx_disable(com) \ pc98_disable_i8251_interrupt(com,IEN_Tx|IEN_TxEMP) #define com_int_Tx_enable(com) \ pc98_enable_i8251_interrupt(com,IEN_TxFLAG) #define com_int_Rx_disable(com) \ pc98_disable_i8251_interrupt(com,IEN_Rx) #define com_int_Rx_enable(com) \ pc98_enable_i8251_interrupt(com,IEN_Rx) #define com_int_TxRx_disable(com) \ pc98_disable_i8251_interrupt(com,IEN_Tx|IEN_TxEMP|IEN_Rx) #define com_int_TxRx_enable(com) \ pc98_enable_i8251_interrupt(com,IEN_TxFLAG|IEN_Rx) #define com_send_break_on(com) \ pc98_i8251_or_cmd(com,CMD8251_SBRK) #define com_send_break_off(com) \ pc98_i8251_clear_cmd(com,CMD8251_SBRK) static struct speedtab pc98speedtab[] = { /* internal RS232C interface */ { 0, 0, }, { 50, 50, }, { 75, 75, }, { 150, 150, }, { 200, 200, }, { 300, 300, }, { 600, 600, }, { 1200, 1200, }, { 2400, 2400, }, { 4800, 4800, }, { 9600, 9600, }, { 19200, 19200, }, { 38400, 38400, }, { 51200, 51200, }, { 76800, 76800, }, { 20800, 20800, }, { 31200, 31200, }, { 41600, 41600, }, { 62400, 62400, }, { -1, -1 } }; static struct speedtab pc98fast_speedtab[] = { { 9600, 0x80 | (DEFAULT_RCLK / (16 * (9600))), }, { 19200, 0x80 | (DEFAULT_RCLK / (16 * (19200))), }, { 38400, 0x80 | (DEFAULT_RCLK / (16 * (38400))), }, { 57600, 0x80 | (DEFAULT_RCLK / (16 * (57600))), }, { 115200, 0x80 | (DEFAULT_RCLK / (16 * (115200))), }, { -1, -1 } }; static struct speedtab comspeedtab_pio9032b[] = { { 300, 6, }, { 600, 5, }, { 1200, 4, }, { 2400, 3, }, { 4800, 2, }, { 9600, 1, }, { 19200, 0, }, { 38400, 7, }, { -1, -1 } }; static struct speedtab comspeedtab_b98_01[] = { { 75, 11, }, { 150, 10, }, { 300, 9, }, { 600, 8, }, { 1200, 7, }, { 2400, 6, }, { 4800, 5, }, { 9600, 4, }, { 19200, 3, }, { 38400, 2, }, { 76800, 1, }, { 153600, 0, }, { -1, -1 } }; static struct speedtab comspeedtab_ind[] = { { 300, 1536, }, { 600, 768, }, { 1200, 384, }, { 2400, 192, }, { 4800, 96, }, { 9600, 48, }, { 19200, 24, }, { 38400, 12, }, { 57600, 8, }, { 115200, 4, }, { 153600, 3, }, { 230400, 2, }, { 460800, 1, }, { -1, -1 } }; struct { char *name; short port_table[7]; short irr_mask; struct speedtab *speedtab; short check_irq; } if_8251_type[] = { /* COM_IF_INTERNAL */ { " (internal)", {0x30, 0x32, 0x32, 0x33, 0x35, -1, -1}, -1, pc98speedtab, 1 }, /* COM_IF_PC9861K_1 */ { " (PC9861K)", {0xb1, 0xb3, 0xb3, 0xb0, 0xb0, -1, -1}, 3, NULL, 1 }, /* COM_IF_PC9861K_2 */ { " (PC9861K)", {0xb9, 0xbb, 0xbb, 0xb2, 0xb2, -1, -1}, 3, NULL, 1 }, /* COM_IF_IND_SS_1 */ { " (IND-SS)", {0xb1, 0xb3, 0xb3, 0xb0, 0xb0, 0xb3, -1}, 3, comspeedtab_ind, 1 }, /* COM_IF_IND_SS_2 */ { " (IND-SS)", {0xb9, 0xbb, 0xbb, 0xb2, 0xb2, 0xbb, -1}, 3, comspeedtab_ind, 1 }, /* COM_IF_PIO9032B_1 */ { " (PIO9032B)", {0xb1, 0xb3, 0xb3, 0xb0, 0xb0, 0xb8, -1}, 7, comspeedtab_pio9032b, 1 }, /* COM_IF_PIO9032B_2 */ { " (PIO9032B)", {0xb9, 0xbb, 0xbb, 0xb2, 0xb2, 0xba, -1}, 7, comspeedtab_pio9032b, 1 }, /* COM_IF_B98_01_1 */ { " (B98-01)", {0xb1, 0xb3, 0xb3, 0xb0, 0xb0, 0xd1, 0xd3}, 7, comspeedtab_b98_01, 0 }, /* COM_IF_B98_01_2 */ { " (B98-01)", {0xb9, 0xbb, 0xbb, 0xb2, 0xb2, 0xd5, 0xd7}, 7, comspeedtab_b98_01, 0 }, }; #define PC98SIO_data_port(type) (if_8251_type[type].port_table[0]) #define PC98SIO_cmd_port(type) (if_8251_type[type].port_table[1]) #define PC98SIO_sts_port(type) (if_8251_type[type].port_table[2]) #define PC98SIO_in_modem_port(type) (if_8251_type[type].port_table[3]) #define PC98SIO_intr_ctrl_port(type) (if_8251_type[type].port_table[4]) #define PC98SIO_baud_rate_port(type) (if_8251_type[type].port_table[5]) #define PC98SIO_func_port(type) (if_8251_type[type].port_table[6]) #define I8251F_data 0x130 #define I8251F_lsr 0x132 #define I8251F_msr 0x134 #define I8251F_iir 0x136 #define I8251F_fcr 0x138 #define I8251F_div 0x13a static bus_addr_t port_table_0[] = {0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007}; static bus_addr_t port_table_1[] = {0x000, 0x002, 0x004, 0x006, 0x008, 0x00a, 0x00c, 0x00e}; static bus_addr_t port_table_8[] = {0x000, 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700}; static bus_addr_t port_table_rsa[] = { 0x008, 0x009, 0x00a, 0x00b, 0x00c, 0x00d, 0x00e, 0x00f, 0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007 }; struct { char *name; short irr_read; short irr_write; bus_addr_t *iat; bus_size_t iatsz; u_long rclk; } if_16550a_type[] = { /* COM_IF_RSA98 */ {" (RSA-98)", -1, -1, port_table_0, IO_COMSIZE, DEFAULT_RCLK}, /* COM_IF_NS16550 */ {"", -1, -1, port_table_0, IO_COMSIZE, DEFAULT_RCLK}, /* COM_IF_SECOND_CCU */ {"", -1, -1, port_table_0, IO_COMSIZE, DEFAULT_RCLK}, /* COM_IF_MC16550II */ {" (MC16550II)", -1, 0x1000, port_table_8, IO_COMSIZE, DEFAULT_RCLK * 4}, /* COM_IF_MCRS98 */ {" (MC-RS98)", -1, 0x1000, port_table_8, IO_COMSIZE, DEFAULT_RCLK * 4}, /* COM_IF_RSB3000 */ {" (RSB-3000)", 0xbf, -1, port_table_1, IO_COMSIZE, DEFAULT_RCLK * 10}, /* COM_IF_RSB384 */ {" (RSB-384)", 0xbf, -1, port_table_1, IO_COMSIZE, DEFAULT_RCLK * 10}, /* COM_IF_MODEM_CARD */ {"", -1, -1, port_table_0, IO_COMSIZE, DEFAULT_RCLK}, /* COM_IF_RSA98III */ {" (RSA-98III)", -1, -1, port_table_rsa, 16, DEFAULT_RCLK * 8}, /* COM_IF_ESP98 */ {" (ESP98)", -1, -1, port_table_1, IO_COMSIZE, DEFAULT_RCLK * 4}, }; #endif /* PC98 */ #ifdef COM_ESP #ifdef PC98 /* XXX configure this properly. */ /* XXX quite broken for new-bus. */ static Port_t likely_com_ports[] = { 0, 0xb0, 0xb1, 0 }; static Port_t likely_esp_ports[] = { 0xc0d0, 0 }; #define ESP98_CMD1 (ESP_CMD1 * 0x100) #define ESP98_CMD2 (ESP_CMD2 * 0x100) #define ESP98_STATUS1 (ESP_STATUS1 * 0x100) #define ESP98_STATUS2 (ESP_STATUS2 * 0x100) #else /* PC98 */ /* XXX configure this properly. */ static Port_t likely_com_ports[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, }; static Port_t likely_esp_ports[] = { 0x140, 0x180, 0x280, 0 }; #endif /* PC98 */ #endif /* * handle sysctl read/write requests for console speed * * In addition to setting comdefaultrate for I/O through /dev/console, * also set the initial and lock values for the /dev/ttyXX device * if there is one associated with the console. Finally, if the /dev/tty * device has already been open, change the speed on the open running port * itself. */ static int sysctl_machdep_comdefaultrate(SYSCTL_HANDLER_ARGS) { int error, s; speed_t newspeed; struct com_s *com; struct tty *tp; newspeed = comdefaultrate; error = sysctl_handle_opaque(oidp, &newspeed, sizeof newspeed, req); if (error || !req->newptr) return (error); comdefaultrate = newspeed; if (comconsole < 0) /* serial console not selected? */ return (0); com = com_addr(comconsole); if (com == NULL) return (ENXIO); /* * set the initial and lock rates for /dev/ttydXX and /dev/cuaXX * (note, the lock rates really are boolean -- if non-zero, disallow * speed changes) */ com->it_in.c_ispeed = com->it_in.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_out.c_ispeed = com->it_out.c_ospeed = com->lt_out.c_ispeed = com->lt_out.c_ospeed = comdefaultrate; /* * if we're open, change the running rate too */ tp = com->tp; if (tp && (tp->t_state & TS_ISOPEN)) { tp->t_termios.c_ispeed = tp->t_termios.c_ospeed = comdefaultrate; s = spltty(); error = comparam(tp, &tp->t_termios); splx(s); } return error; } SYSCTL_PROC(_machdep, OID_AUTO, conspeed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_machdep_comdefaultrate, "I", ""); /* * Unload the driver and clear the table. * XXX this is mostly wrong. * XXX TODO: * This is usually called when the card is ejected, but * can be caused by a kldunload of a controller driver. * The idea is to reset the driver's view of the device * and ensure that any driver entry points such as * read and write do not hang. */ int siodetach(dev) device_t dev; { struct com_s *com; int i; com = (struct com_s *) device_get_softc(dev); if (com == NULL) { device_printf(dev, "NULL com in siounload\n"); return (0); } com->gone = TRUE; for (i = 0 ; i < 6; i++) destroy_dev(com->devs[i]); if (com->irqres) { bus_teardown_intr(dev, com->irqres, com->cookie); bus_release_resource(dev, SYS_RES_IRQ, 0, com->irqres); } if (com->ioportres) bus_release_resource(dev, SYS_RES_IOPORT, com->ioportrid, com->ioportres); if (com->tp && (com->tp->t_state & TS_ISOPEN)) { device_printf(dev, "still open, forcing close\n"); ttyld_close(com->tp, 0); ttyclose(com->tp); } else { if (com->ibuf != NULL) free(com->ibuf, M_DEVBUF); #ifdef PC98 if (com->obuf1 != NULL) free(com->obuf1, M_DEVBUF); #endif device_set_softc(dev, NULL); free(com, M_DEVBUF); } return (0); } int sioprobe(dev, xrid, rclk, noprobe) device_t dev; int xrid; u_long rclk; int noprobe; { #if 0 static bool_t already_init; device_t xdev; #endif struct com_s *com; u_int divisor; bool_t failures[10]; int fn; device_t idev; Port_t iobase; intrmask_t irqmap[4]; intrmask_t irqs; u_char mcr_image; int result; u_long xirq; u_int flags = device_get_flags(dev); int rid; struct resource *port; #ifdef PC98 int tmp; struct siodev iod; #endif #ifdef PC98 iod.if_type = GET_IFTYPE(flags); if ((iod.if_type < 0 || iod.if_type > COM_IF_END1) && (iod.if_type < 0x10 || iod.if_type > COM_IF_END2)) return ENXIO; #endif rid = xrid; #ifdef PC98 if (IS_8251(iod.if_type)) { port = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); } else if (iod.if_type == COM_IF_MODEM_CARD || iod.if_type == COM_IF_RSA98III || isa_get_vendorid(dev)) { port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, if_16550a_type[iod.if_type & 0x0f].iatsz, RF_ACTIVE); } else { port = isa_alloc_resourcev(dev, SYS_RES_IOPORT, &rid, if_16550a_type[iod.if_type & 0x0f].iat, if_16550a_type[iod.if_type & 0x0f].iatsz, RF_ACTIVE); } #else port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, IO_COMSIZE, RF_ACTIVE); #endif if (!port) return (ENXIO); #ifdef PC98 if (!IS_8251(iod.if_type)) { if (isa_load_resourcev(port, if_16550a_type[iod.if_type & 0x0f].iat, if_16550a_type[iod.if_type & 0x0f].iatsz) != 0) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return ENXIO; } } #endif com = malloc(sizeof(*com), M_DEVBUF, M_NOWAIT | M_ZERO); if (com == NULL) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return (ENOMEM); } device_set_softc(dev, com); com->bst = rman_get_bustag(port); com->bsh = rman_get_bushandle(port); #ifdef PC98 if (!IS_8251(iod.if_type) && rclk == 0) rclk = if_16550a_type[iod.if_type & 0x0f].rclk; #else if (rclk == 0) rclk = DEFAULT_RCLK; #endif com->rclk = rclk; while (sio_inited != 2) if (atomic_cmpset_int(&sio_inited, 0, 1)) { mtx_init(&sio_lock, sio_driver_name, NULL, (comconsole != -1) ? MTX_SPIN | MTX_QUIET : MTX_SPIN); atomic_store_rel_int(&sio_inited, 2); } #if 0 /* * XXX this is broken - when we are first called, there are no * previously configured IO ports. We could hard code * 0x3f8, 0x2f8, 0x3e8, 0x2e8 etc but that's probably worse. * This code has been doing nothing since the conversion since * "count" is zero the first time around. */ if (!already_init) { /* * Turn off MCR_IENABLE for all likely serial ports. An unused * port with its MCR_IENABLE gate open will inhibit interrupts * from any used port that shares the interrupt vector. * XXX the gate enable is elsewhere for some multiports. */ device_t *devs; int count, i, xioport; #ifdef PC98 int xiftype; #endif devclass_get_devices(sio_devclass, &devs, &count); #ifdef PC98 for (i = 0; i < count; i++) { xdev = devs[i]; xioport = bus_get_resource_start(xdev, SYS_RES_IOPORT, 0); xiftype = GET_IFTYPE(device_get_flags(xdev)); if (device_is_enabled(xdev) && xioport > 0) { if (IS_8251(xiftype)) outb((xioport & 0xff00) | PC98SIO_cmd_port(xiftype & 0x0f), 0xf2); else outb(xioport + if_16550a_type[xiftype & 0x0f].iat[com_mcr], 0); } } #else for (i = 0; i < count; i++) { xdev = devs[i]; if (device_is_enabled(xdev) && bus_get_resource(xdev, SYS_RES_IOPORT, 0, &xioport, NULL) == 0) outb(xioport + com_mcr, 0); } #endif free(devs, M_TEMP); already_init = TRUE; } #endif if (COM_LLCONSOLE(flags)) { printf("sio%d: reserved for low-level i/o\n", device_get_unit(dev)); bus_release_resource(dev, SYS_RES_IOPORT, rid, port); device_set_softc(dev, NULL); free(com, M_DEVBUF); return (ENXIO); } #ifdef PC98 DELAY(10); /* * If the port is i8251 UART (internal, B98_01) */ if (pc98_check_if_type(dev, &iod) == -1) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); device_set_softc(dev, NULL); free(com, M_DEVBUF); return (ENXIO); } if (iod.irq > 0) bus_set_resource(dev, SYS_RES_IRQ, 0, iod.irq, 1); if (IS_8251(iod.if_type)) { outb(iod.cmd, 0); DELAY(10); outb(iod.cmd, 0); DELAY(10); outb(iod.cmd, 0); DELAY(10); outb(iod.cmd, CMD8251_RESET); DELAY(1000); /* for a while...*/ outb(iod.cmd, 0xf2); /* MODE (dummy) */ DELAY(10); outb(iod.cmd, 0x01); /* CMD (dummy) */ DELAY(1000); /* for a while...*/ if (( inb(iod.sts) & STS8251_TxEMP ) == 0 ) { result = (ENXIO); } if (if_8251_type[iod.if_type & 0x0f].check_irq) { COM_INT_DISABLE tmp = ( inb( iod.ctrl ) & ~(IEN_Rx|IEN_TxEMP|IEN_Tx)); outb( iod.ctrl, tmp|IEN_TxEMP ); DELAY(10); result = isa_irq_pending() ? 0 : ENXIO; outb( iod.ctrl, tmp ); COM_INT_ENABLE } else { /* * B98_01 doesn't activate TxEMP interrupt line * when being reset, so we can't check irq pending. */ result = 0; } if (epson_machine_id==0x20) { /* XXX */ result = 0; } bus_release_resource(dev, SYS_RES_IOPORT, rid, port); if (result) { device_set_softc(dev, NULL); free(com, M_DEVBUF); } return result; } #endif /* PC98 */ /* * If the device is on a multiport card and has an AST/4 * compatible interrupt control register, initialize this * register and prepare to leave MCR_IENABLE clear in the mcr. * Otherwise, prepare to set MCR_IENABLE in the mcr. * Point idev to the device struct giving the correct id_irq. * This is the struct for the master device if there is one. */ idev = dev; mcr_image = MCR_IENABLE; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(flags)) { #ifndef PC98 Port_t xiobase; u_long io; #endif idev = devclass_get_device(sio_devclass, COM_MPMASTER(flags)); if (idev == NULL) { printf("sio%d: master device %d not configured\n", device_get_unit(dev), COM_MPMASTER(flags)); idev = dev; } #ifndef PC98 if (!COM_NOTAST4(flags)) { if (bus_get_resource(idev, SYS_RES_IOPORT, 0, &io, NULL) == 0) { xiobase = io; if (bus_get_resource(idev, SYS_RES_IRQ, 0, NULL, NULL) == 0) outb(xiobase + com_scr, 0x80); else outb(xiobase + com_scr, 0); } mcr_image = 0; } #endif } #endif /* COM_MULTIPORT */ if (bus_get_resource(idev, SYS_RES_IRQ, 0, NULL, NULL) != 0) mcr_image = 0; bzero(failures, sizeof failures); iobase = rman_get_start(port); #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) { mcr_image = 0; outb(iobase + rsa_msr, 0x04); outb(iobase + rsa_frr, 0x00); if ((inb(iobase + rsa_srr) & 0x36) != 0x36) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); device_set_softc(dev, NULL); free(com, M_DEVBUF); return (ENXIO); } outb(iobase + rsa_ier, 0x00); outb(iobase + rsa_frr, 0x00); outb(iobase + rsa_tivsr, 0x00); outb(iobase + rsa_tcr, 0x00); } tmp = if_16550a_type[iod.if_type & 0x0f].irr_write; if (tmp != -1) { /* MC16550II */ int irqout; switch (isa_get_irq(idev)) { case 3: irqout = 4; break; case 5: irqout = 5; break; case 6: irqout = 6; break; case 12: irqout = 7; break; default: printf("sio%d: irq configuration error\n", device_get_unit(dev)); bus_release_resource(dev, SYS_RES_IOPORT, rid, port); device_set_softc(dev, NULL); free(com, M_DEVBUF); return (ENXIO); } outb((iobase & 0x00ff) | tmp, irqout); } #endif /* * We don't want to get actual interrupts, just masked ones. * Interrupts from this line should already be masked in the ICU, * but mask them in the processor as well in case there are some * (misconfigured) shared interrupts. */ mtx_lock_spin(&sio_lock); /* EXTRA DELAY? */ /* * Initialize the speed and the word size and wait long enough to * drain the maximum of 16 bytes of junk in device output queues. * The speed is undefined after a master reset and must be set * before relying on anything related to output. There may be * junk after a (very fast) soft reboot and (apparently) after * master reset. * XXX what about the UART bug avoided by waiting in comparam()? * We don't want to to wait long enough to drain at 2 bps. */ if (iobase == siocniobase) DELAY((16 + 1) * 1000000 / (comdefaultrate / 10)); else { sio_setreg(com, com_cfcr, CFCR_DLAB | CFCR_8BITS); divisor = siodivisor(rclk, SIO_TEST_SPEED); sio_setreg(com, com_dlbl, divisor & 0xff); sio_setreg(com, com_dlbh, divisor >> 8); sio_setreg(com, com_cfcr, CFCR_8BITS); DELAY((16 + 1) * 1000000 / (SIO_TEST_SPEED / 10)); } /* * Enable the interrupt gate and disable device interupts. This * should leave the device driving the interrupt line low and * guarantee an edge trigger if an interrupt can be generated. */ /* EXTRA DELAY? */ sio_setreg(com, com_mcr, mcr_image); sio_setreg(com, com_ier, 0); DELAY(1000); /* XXX */ irqmap[0] = isa_irq_pending(); /* * Attempt to set loopback mode so that we can send a null byte * without annoying any external device. */ /* EXTRA DELAY? */ sio_setreg(com, com_mcr, mcr_image | MCR_LOOPBACK); /* * Attempt to generate an output interrupt. On 8250's, setting * IER_ETXRDY generates an interrupt independent of the current * setting and independent of whether the THR is empty. On 16450's, * setting IER_ETXRDY generates an interrupt independent of the * current setting. On 16550A's, setting IER_ETXRDY only * generates an interrupt when IER_ETXRDY is not already set. */ sio_setreg(com, com_ier, IER_ETXRDY); #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) outb(iobase + rsa_ier, 0x04); #endif /* * On some 16x50 incompatibles, setting IER_ETXRDY doesn't generate * an interrupt. They'd better generate one for actually doing * output. Loopback may be broken on the same incompatibles but * it's unlikely to do more than allow the null byte out. */ sio_setreg(com, com_data, 0); if (iobase == siocniobase) DELAY((1 + 2) * 1000000 / (comdefaultrate / 10)); else DELAY((1 + 2) * 1000000 / (SIO_TEST_SPEED / 10)); /* * Turn off loopback mode so that the interrupt gate works again * (MCR_IENABLE was hidden). This should leave the device driving * an interrupt line high. It doesn't matter if the interrupt * line oscillates while we are not looking at it, since interrupts * are disabled. */ /* EXTRA DELAY? */ sio_setreg(com, com_mcr, mcr_image); /* * It seems my Xircom CBEM56G Cardbus modem wants to be reset * to 8 bits *again*, or else probe test 0 will fail. * gwk@sgi.com, 4/19/2001 */ sio_setreg(com, com_cfcr, CFCR_8BITS); /* * Some PCMCIA cards (Palido 321s, DC-1S, ...) have the "TXRDY bug", * so we probe for a buggy IIR_TXRDY implementation even in the * noprobe case. We don't probe for it in the !noprobe case because * noprobe is always set for PCMCIA cards and the problem is not * known to affect any other cards. */ if (noprobe) { /* Read IIR a few times. */ for (fn = 0; fn < 2; fn ++) { DELAY(10000); failures[6] = sio_getreg(com, com_iir); } /* IIR_TXRDY should be clear. Is it? */ result = 0; if (failures[6] & IIR_TXRDY) { /* * No. We seem to have the bug. Does our fix for * it work? */ sio_setreg(com, com_ier, 0); if (sio_getreg(com, com_iir) & IIR_NOPEND) { /* Yes. We discovered the TXRDY bug! */ SET_FLAG(dev, COM_C_IIR_TXRDYBUG); } else { /* No. Just fail. XXX */ result = ENXIO; sio_setreg(com, com_mcr, 0); } } else { /* Yes. No bug. */ CLR_FLAG(dev, COM_C_IIR_TXRDYBUG); } sio_setreg(com, com_ier, 0); sio_setreg(com, com_cfcr, CFCR_8BITS); mtx_unlock_spin(&sio_lock); bus_release_resource(dev, SYS_RES_IOPORT, rid, port); if (iobase == siocniobase) result = 0; if (result != 0) { device_set_softc(dev, NULL); free(com, M_DEVBUF); } return (result); } /* * Check that * o the CFCR, IER and MCR in UART hold the values written to them * (the values happen to be all distinct - this is good for * avoiding false positive tests from bus echoes). * o an output interrupt is generated and its vector is correct. * o the interrupt goes away when the IIR in the UART is read. */ /* EXTRA DELAY? */ failures[0] = sio_getreg(com, com_cfcr) - CFCR_8BITS; failures[1] = sio_getreg(com, com_ier) - IER_ETXRDY; failures[2] = sio_getreg(com, com_mcr) - mcr_image; DELAY(10000); /* Some internal modems need this time */ irqmap[1] = isa_irq_pending(); failures[4] = (sio_getreg(com, com_iir) & IIR_IMASK) - IIR_TXRDY; #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) inb(iobase + rsa_srr); #endif DELAY(1000); /* XXX */ irqmap[2] = isa_irq_pending(); failures[6] = (sio_getreg(com, com_iir) & IIR_IMASK) - IIR_NOPEND; #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) inb(iobase + rsa_srr); #endif /* * Turn off all device interrupts and check that they go off properly. * Leave MCR_IENABLE alone. For ports without a master port, it gates * the OUT2 output of the UART to * the ICU input. Closing the gate would give a floating ICU input * (unless there is another device driving it) and spurious interrupts. * (On the system that this was first tested on, the input floats high * and gives a (masked) interrupt as soon as the gate is closed.) */ sio_setreg(com, com_ier, 0); sio_setreg(com, com_cfcr, CFCR_8BITS); /* dummy to avoid bus echo */ failures[7] = sio_getreg(com, com_ier); #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) outb(iobase + rsa_ier, 0x00); #endif DELAY(1000); /* XXX */ irqmap[3] = isa_irq_pending(); failures[9] = (sio_getreg(com, com_iir) & IIR_IMASK) - IIR_NOPEND; #ifdef PC98 if (iod.if_type == COM_IF_RSA98III) { inb(iobase + rsa_srr); outb(iobase + rsa_frr, 0x00); } #endif mtx_unlock_spin(&sio_lock); irqs = irqmap[1] & ~irqmap[0]; if (bus_get_resource(idev, SYS_RES_IRQ, 0, &xirq, NULL) == 0 && ((1 << xirq) & irqs) == 0) { printf( "sio%d: configured irq %ld not in bitmap of probed irqs %#x\n", device_get_unit(dev), xirq, irqs); printf( "sio%d: port may not be enabled\n", device_get_unit(dev)); } if (bootverbose) printf("sio%d: irq maps: %#x %#x %#x %#x\n", device_get_unit(dev), irqmap[0], irqmap[1], irqmap[2], irqmap[3]); result = 0; for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) { sio_setreg(com, com_mcr, 0); result = ENXIO; if (bootverbose) { printf("sio%d: probe failed test(s):", device_get_unit(dev)); for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) printf(" %d", fn); printf("\n"); } break; } bus_release_resource(dev, SYS_RES_IOPORT, rid, port); if (iobase == siocniobase) result = 0; if (result != 0) { device_set_softc(dev, NULL); free(com, M_DEVBUF); } return (result); } #ifdef COM_ESP static int espattach(com, esp_port) struct com_s *com; Port_t esp_port; { u_char dips; u_char val; /* * Check the ESP-specific I/O port to see if we're an ESP * card. If not, return failure immediately. */ if ((inb(esp_port) & 0xf3) == 0) { printf(" port 0x%x is not an ESP board?\n", esp_port); return (0); } /* * We've got something that claims to be a Hayes ESP card. * Let's hope so. */ /* Get the dip-switch configuration */ #ifdef PC98 outb(esp_port + ESP98_CMD1, ESP_GETDIPS); dips = inb(esp_port + ESP98_STATUS1); #else outb(esp_port + ESP_CMD1, ESP_GETDIPS); dips = inb(esp_port + ESP_STATUS1); #endif /* * Bits 0,1 of dips say which COM port we are. */ #ifdef PC98 if ((rman_get_start(com->ioportres) & 0xff) == likely_com_ports[dips & 0x03]) #else if (rman_get_start(com->ioportres) == likely_com_ports[dips & 0x03]) #endif printf(" : ESP"); else { printf(" esp_port has com %d\n", dips & 0x03); return (0); } /* * Check for ESP version 2.0 or later: bits 4,5,6 = 010. */ #ifdef PC98 outb(esp_port + ESP98_CMD1, ESP_GETTEST); val = inb(esp_port + ESP98_STATUS1); /* clear reg 1 */ val = inb(esp_port + ESP98_STATUS2); #else outb(esp_port + ESP_CMD1, ESP_GETTEST); val = inb(esp_port + ESP_STATUS1); /* clear reg 1 */ val = inb(esp_port + ESP_STATUS2); #endif if ((val & 0x70) < 0x20) { printf("-old (%o)", val & 0x70); return (0); } /* * Check for ability to emulate 16550: bit 7 == 1 */ if ((dips & 0x80) == 0) { printf(" slave"); return (0); } /* * Okay, we seem to be a Hayes ESP card. Whee. */ com->esp = TRUE; com->esp_port = esp_port; return (1); } #endif /* COM_ESP */ int sioattach(dev, xrid, rclk) device_t dev; int xrid; u_long rclk; { struct com_s *com; #ifdef COM_ESP Port_t *espp; #endif Port_t iobase; int minorbase; int unit; u_int flags; int rid; struct resource *port; int ret; #ifdef PC98 u_char *obuf; u_long obufsize; int if_type = GET_IFTYPE(device_get_flags(dev)); #endif rid = xrid; #ifdef PC98 if (IS_8251(if_type)) { port = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); } else if (if_type == COM_IF_MODEM_CARD || if_type == COM_IF_RSA98III || isa_get_vendorid(dev)) { port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, if_16550a_type[if_type & 0x0f].iatsz, RF_ACTIVE); } else { port = isa_alloc_resourcev(dev, SYS_RES_IOPORT, &rid, if_16550a_type[if_type & 0x0f].iat, if_16550a_type[if_type & 0x0f].iatsz, RF_ACTIVE); } #else port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, IO_COMSIZE, RF_ACTIVE); #endif if (!port) return (ENXIO); #ifdef PC98 if (!IS_8251(if_type)) { if (isa_load_resourcev(port, if_16550a_type[if_type & 0x0f].iat, if_16550a_type[if_type & 0x0f].iatsz) != 0) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return ENXIO; } } #endif iobase = rman_get_start(port); unit = device_get_unit(dev); com = device_get_softc(dev); flags = device_get_flags(dev); if (unit >= sio_numunits) sio_numunits = unit + 1; #ifdef PC98 obufsize = 256; if (if_type == COM_IF_RSA98III) obufsize = 2048; if ((obuf = malloc(obufsize * 2, M_DEVBUF, M_NOWAIT)) == NULL) { bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return ENXIO; } bzero(obuf, obufsize * 2); #endif /* * sioprobe() has initialized the device registers as follows: * o cfcr = CFCR_8BITS. * It is most important that CFCR_DLAB is off, so that the * data port is not hidden when we enable interrupts. * o ier = 0. * Interrupts are only enabled when the line is open. * o mcr = MCR_IENABLE, or 0 if the port has AST/4 compatible * interrupt control register or the config specifies no irq. * Keeping MCR_DTR and MCR_RTS off might stop the external * device from sending before we are ready. */ bzero(com, sizeof *com); com->unit = unit; com->ioportres = port; com->ioportrid = rid; com->bst = rman_get_bustag(port); com->bsh = rman_get_bushandle(port); com->cfcr_image = CFCR_8BITS; com->dtr_wait = 3 * hz; com->loses_outints = COM_LOSESOUTINTS(flags) != 0; com->no_irq = bus_get_resource(dev, SYS_RES_IRQ, 0, NULL, NULL) != 0; com->tx_fifo_size = 1; #ifdef PC98 com->obufsize = obufsize; com->obuf1 = obuf; com->obuf2 = obuf + obufsize; #endif com->obufs[0].l_head = com->obuf1; com->obufs[1].l_head = com->obuf2; #ifdef PC98 com->pc98_if_type = if_type; if (IS_8251(if_type)) { pc98_set_ioport(com); if (if_type == COM_IF_INTERNAL && pc98_check_8251fifo()) { com->pc98_8251fifo = 1; com->pc98_8251fifo_enable = 0; } } else { bus_addr_t *iat = if_16550a_type[if_type & 0x0f].iat; com->data_port = iobase + iat[com_data]; com->int_ctl_port = iobase + iat[com_ier]; com->int_id_port = iobase + iat[com_iir]; com->modem_ctl_port = iobase + iat[com_mcr]; com->mcr_image = inb(com->modem_ctl_port); com->line_status_port = iobase + iat[com_lsr]; com->modem_status_port = iobase + iat[com_msr]; } #else /* not PC98 */ com->data_port = iobase + com_data; com->int_ctl_port = iobase + com_ier; com->int_id_port = iobase + com_iir; com->modem_ctl_port = iobase + com_mcr; com->mcr_image = inb(com->modem_ctl_port); com->line_status_port = iobase + com_lsr; com->modem_status_port = iobase + com_msr; #endif #ifdef PC98 if (!IS_8251(if_type) && rclk == 0) rclk = if_16550a_type[if_type & 0x0f].rclk; #else if (rclk == 0) rclk = DEFAULT_RCLK; #endif com->rclk = rclk; /* * We don't use all the flags from 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. */ com->it_in.c_iflag = 0; com->it_in.c_oflag = 0; com->it_in.c_cflag = TTYDEF_CFLAG; com->it_in.c_lflag = 0; if (unit == comconsole) { #ifdef PC98 if (IS_8251(com->pc98_if_type)) DELAY(100000); #endif com->it_in.c_iflag = TTYDEF_IFLAG; com->it_in.c_oflag = TTYDEF_OFLAG; com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; com->it_in.c_lflag = TTYDEF_LFLAG; com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; com->lt_out.c_ispeed = com->lt_out.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; } else com->it_in.c_ispeed = com->it_in.c_ospeed = TTYDEF_SPEED; if (siosetwater(com, com->it_in.c_ispeed) != 0) { mtx_unlock_spin(&sio_lock); /* * Leave i/o resources allocated if this is a `cn'-level * console, so that other devices can't snarf them. */ if (iobase != siocniobase) bus_release_resource(dev, SYS_RES_IOPORT, rid, port); return (ENOMEM); } mtx_unlock_spin(&sio_lock); termioschars(&com->it_in); com->it_out = com->it_in; /* attempt to determine UART type */ printf("sio%d: type", unit); #ifndef PC98 if (!COM_ISMULTIPORT(flags) && !COM_IIR_TXRDYBUG(flags) && !COM_NOSCR(flags)) { u_char scr; u_char scr1; u_char scr2; scr = sio_getreg(com, com_scr); sio_setreg(com, com_scr, 0xa5); scr1 = sio_getreg(com, com_scr); sio_setreg(com, com_scr, 0x5a); scr2 = sio_getreg(com, com_scr); sio_setreg(com, com_scr, scr); if (scr1 != 0xa5 || scr2 != 0x5a) { printf(" 8250 or not responding"); goto determined_type; } } #endif /* !PC98 */ #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if (com->pc98_8251fifo && !COM_NOFIFO(flags)) com->tx_fifo_size = 16; com_int_TxRx_disable( com ); com_cflag_and_speed_set( com, com->it_in.c_cflag, comdefaultrate ); com_tiocm_bic( com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE ); com_send_break_off( com ); if (com->pc98_if_type == COM_IF_INTERNAL) { printf(" (internal%s%s)", com->pc98_8251fifo ? " fifo" : "", PC98SIO_baud_rate_port(com->pc98_if_type) != -1 ? " v-fast" : ""); } else { printf(" 8251%s", if_8251_type[com->pc98_if_type & 0x0f].name); } } else { #endif /* PC98 */ sio_setreg(com, com_fifo, FIFO_ENABLE | FIFO_RX_HIGH); DELAY(100); switch (inb(com->int_id_port) & IIR_FIFO_MASK) { case FIFO_RX_LOW: printf(" 16450"); break; case FIFO_RX_MEDL: printf(" 16450?"); break; case FIFO_RX_MEDH: printf(" 16550?"); break; case FIFO_RX_HIGH: if (COM_NOFIFO(flags)) { printf(" 16550A fifo disabled"); break; } com->hasfifo = TRUE; #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) { com->tx_fifo_size = 2048; com->rsabase = iobase; outb(com->rsabase + rsa_ier, 0x00); outb(com->rsabase + rsa_frr, 0x00); } #else if (COM_ST16650A(flags)) { printf(" ST16650A"); com->st16650a = TRUE; com->tx_fifo_size = 32; break; } if (COM_TI16754(flags)) { printf(" TI16754"); com->tx_fifo_size = 64; break; } #endif printf(" 16550A"); #ifdef COM_ESP #ifdef PC98 if (com->pc98_if_type == COM_IF_ESP98) #endif for (espp = likely_esp_ports; *espp != 0; espp++) if (espattach(com, *espp)) { com->tx_fifo_size = 1024; break; } if (com->esp) break; #endif #ifdef PC98 com->tx_fifo_size = 16; #else com->tx_fifo_size = COM_FIFOSIZE(flags); if (com->tx_fifo_size == 0) com->tx_fifo_size = 16; else printf(" lookalike with %u bytes FIFO", com->tx_fifo_size); #endif break; } #ifdef PC98 if (com->pc98_if_type == COM_IF_RSB3000) { /* Set RSB-2000/3000 Extended Buffer mode. */ u_char lcr; lcr = sio_getreg(com, com_cfcr); sio_setreg(com, com_cfcr, lcr | CFCR_DLAB); sio_setreg(com, com_emr, EMR_EXBUFF | EMR_EFMODE); sio_setreg(com, com_cfcr, lcr); } #endif #ifdef COM_ESP if (com->esp) { /* * Set 16550 compatibility mode. * We don't use the ESP_MODE_SCALE bit to increase the * fifo trigger levels because we can't handle large * bursts of input. * XXX flow control should be set in comparam(), not here. */ #ifdef PC98 outb(com->esp_port + ESP98_CMD1, ESP_SETMODE); outb(com->esp_port + ESP98_CMD2, ESP_MODE_RTS | ESP_MODE_FIFO); #else outb(com->esp_port + ESP_CMD1, ESP_SETMODE); outb(com->esp_port + ESP_CMD2, ESP_MODE_RTS | ESP_MODE_FIFO); #endif /* Set RTS/CTS flow control. */ #ifdef PC98 outb(com->esp_port + ESP98_CMD1, ESP_SETFLOWTYPE); outb(com->esp_port + ESP98_CMD2, ESP_FLOW_RTS); outb(com->esp_port + ESP98_CMD2, ESP_FLOW_CTS); #else outb(com->esp_port + ESP_CMD1, ESP_SETFLOWTYPE); outb(com->esp_port + ESP_CMD2, ESP_FLOW_RTS); outb(com->esp_port + ESP_CMD2, ESP_FLOW_CTS); #endif /* Set flow-control levels. */ #ifdef PC98 outb(com->esp_port + ESP98_CMD1, ESP_SETRXFLOW); outb(com->esp_port + ESP98_CMD2, HIBYTE(768)); outb(com->esp_port + ESP98_CMD2, LOBYTE(768)); outb(com->esp_port + ESP98_CMD2, HIBYTE(512)); outb(com->esp_port + ESP98_CMD2, LOBYTE(512)); #else outb(com->esp_port + ESP_CMD1, ESP_SETRXFLOW); outb(com->esp_port + ESP_CMD2, HIBYTE(768)); outb(com->esp_port + ESP_CMD2, LOBYTE(768)); outb(com->esp_port + ESP_CMD2, HIBYTE(512)); outb(com->esp_port + ESP_CMD2, LOBYTE(512)); #endif #ifdef PC98 /* Set UART clock prescaler. */ outb(com->esp_port + ESP98_CMD1, ESP_SETCLOCK); outb(com->esp_port + ESP98_CMD2, 2); /* 4 times */ #endif } #endif /* COM_ESP */ sio_setreg(com, com_fifo, 0); #ifdef PC98 printf("%s", if_16550a_type[com->pc98_if_type & 0x0f].name); #else determined_type: ; #endif #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(flags)) { device_t masterdev; com->multiport = TRUE; printf(" (multiport"); if (unit == COM_MPMASTER(flags)) printf(" master"); printf(")"); masterdev = devclass_get_device(sio_devclass, COM_MPMASTER(flags)); com->no_irq = (masterdev == NULL || bus_get_resource(masterdev, SYS_RES_IRQ, 0, NULL, NULL) != 0); } #endif /* COM_MULTIPORT */ #ifdef PC98 } #endif if (unit == comconsole) printf(", console"); if (COM_IIR_TXRDYBUG(flags)) printf(" with a buggy IIR_TXRDY implementation"); printf("\n"); if (sio_fast_ih == NULL) { swi_add(&tty_ithd, "tty:sio", siopoll, NULL, SWI_TTY, 0, &sio_fast_ih); swi_add(&clk_ithd, "tty:sio", siopoll, NULL, SWI_TTY, 0, &sio_slow_ih); } minorbase = UNIT_TO_MINOR(unit); com->devs[0] = make_dev(&sio_cdevsw, minorbase, UID_ROOT, GID_WHEEL, 0600, "ttyd%r", unit); com->devs[1] = make_dev(&sio_cdevsw, minorbase | CONTROL_INIT_STATE, UID_ROOT, GID_WHEEL, 0600, "ttyid%r", unit); com->devs[2] = make_dev(&sio_cdevsw, minorbase | CONTROL_LOCK_STATE, UID_ROOT, GID_WHEEL, 0600, "ttyld%r", unit); com->devs[3] = make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK, UID_UUCP, GID_DIALER, 0660, "cuaa%r", unit); com->devs[4] = make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK | CONTROL_INIT_STATE, UID_UUCP, GID_DIALER, 0660, "cuaia%r", unit); com->devs[5] = make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK | CONTROL_LOCK_STATE, UID_UUCP, GID_DIALER, 0660, "cuala%r", unit); for (rid = 0; rid < 6; rid++) com->devs[rid]->si_drv1 = com; com->flags = flags; com->pps.ppscap = PPS_CAPTUREASSERT | PPS_CAPTURECLEAR; if (COM_PPSCTS(flags)) com->pps_bit = MSR_CTS; else com->pps_bit = MSR_DCD; pps_init(&com->pps); rid = 0; com->irqres = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (com->irqres) { ret = BUS_SETUP_INTR(device_get_parent(dev), dev, com->irqres, INTR_TYPE_TTY | INTR_FAST, siointr, com, &com->cookie); if (ret) { ret = BUS_SETUP_INTR(device_get_parent(dev), dev, com->irqres, INTR_TYPE_TTY, siointr, com, &com->cookie); if (ret == 0) device_printf(dev, "unable to activate interrupt in fast mode - using normal mode\n"); } if (ret) device_printf(dev, "could not activate interrupt\n"); #if defined(DDB) && (defined(BREAK_TO_DEBUGGER) || \ defined(ALT_BREAK_TO_DEBUGGER)) /* * Enable interrupts for early break-to-debugger support * on the console. */ if (ret == 0 && unit == comconsole) outb(siocniobase + com_ier, IER_ERXRDY | IER_ERLS | IER_EMSC); #endif } return (0); } static int sioopen(dev, flag, mode, td) dev_t dev; int flag; int mode; struct thread *td; { struct com_s *com; int error; int mynor; int s; struct tty *tp; int unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); if (com == NULL) return (ENXIO); if (com->gone) return (ENXIO); if (mynor & CONTROL_MASK) return (0); tp = dev->si_tty = com->tp = ttymalloc(com->tp); s = spltty(); /* * We jump to this label after all non-interrupted sleeps to pick * up any changes of the device state. */ open_top: while (com->state & CS_DTR_OFF) { error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "siodtr", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!com->active_out) { error = EBUSY; goto out; } } else { if (com->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&com->active_out, TTIPRI | PCATCH, "siobi", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && suser(td)) { 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 = comstart; tp->t_param = comparam; tp->t_stop = comstop; tp->t_dev = dev; tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in; #ifdef PC98 if (!IS_8251(com->pc98_if_type)) #endif (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); com->poll = com->no_irq; com->poll_output = com->loses_outints; ++com->wopeners; error = comparam(tp, &tp->t_termios); --com->wopeners; if (error != 0) goto out; #ifdef PC98 if (IS_8251(com->pc98_if_type)) { com_tiocm_bis(com, TIOCM_DTR|TIOCM_RTS); pc98_msrint_start(dev); if (com->pc98_8251fifo) { com->pc98_8251fifo_enable = 1; outb(I8251F_fcr, CTRL8251F_ENABLE | CTRL8251F_XMT_RST | CTRL8251F_RCV_RST); } } #endif /* * XXX we should goto open_top if comparam() slept. */ if (com->hasfifo) { int i; /* * (Re)enable and drain fifos. * * Certain SMC chips cause problems if the fifos * are enabled while input is ready. Turn off the * fifo if necessary to clear the input. We test * the input ready bit after enabling the fifos * since we've already enabled them in comparam() * and to handle races between enabling and fresh * input. */ for (i = 0; i < 500; i++) { sio_setreg(com, com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image); #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) outb(com->rsabase + rsa_frr , 0x00); #endif /* * XXX the delays are for superstitious * historical reasons. It must be less than * the character time at the maximum * supported speed (87 usec at 115200 bps * 8N1). Otherwise we might loop endlessly * if data is streaming in. We used to use * delays of 100. That usually worked * because DELAY(100) used to usually delay * for about 85 usec instead of 100. */ DELAY(50); #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III ? !(inb(com->rsabase + rsa_srr) & 0x08) : !(inb(com->line_status_port) & LSR_RXRDY)) break; #else if (!(inb(com->line_status_port) & LSR_RXRDY)) break; #endif sio_setreg(com, com_fifo, 0); DELAY(50); (void) inb(com->data_port); } if (i == 500) { error = EIO; goto out; } } mtx_lock_spin(&sio_lock); #ifdef PC98 if (IS_8251(com->pc98_if_type)) { com_tiocm_bis(com, TIOCM_LE); com->pc98_prev_modem_status = pc98_get_modem_status(com); com_int_Rx_enable(com); } else { #endif (void) inb(com->line_status_port); (void) inb(com->data_port); com->prev_modem_status = com->last_modem_status = inb(com->modem_status_port); outb(com->int_ctl_port, IER_ERXRDY | IER_ERLS | IER_EMSC | (COM_IIR_TXRDYBUG(com->flags) ? 0 : IER_ETXRDY)); #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) { outb(com->rsabase + rsa_ier, 0x1d); outb(com->int_ctl_port, IER_ERLS | IER_EMSC); } #endif #ifdef PC98 } #endif mtx_unlock_spin(&sio_lock); /* * Handle initial DCD. Callout devices get a fake initial * DCD (trapdoor DCD). If we are callout, then any sleeping * callin opens get woken up and resume sleeping on "siobi" * instead of "siodcd". */ /* * XXX `mynor & CALLOUT_MASK' should be * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where * TRAPDOOR_CARRIER is the default initial state for callout * devices and SOFT_CARRIER is like CLOCAL except it hides * the true carrier. */ #ifdef PC98 if ((IS_8251(com->pc98_if_type) && (pc98_get_modem_status(com) & TIOCM_CAR)) || (!IS_8251(com->pc98_if_type) && (com->prev_modem_status & MSR_DCD)) || mynor & CALLOUT_MASK) ttyld_modem(tp, 1); #else if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) ttyld_modem(tp, 1); #endif } /* * Wait for DCD if necessary. */ if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++com->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "siodcd", 0); if (com_addr(unit) == NULL) return (ENXIO); --com->wopeners; if (error != 0 || com->gone) goto out; goto open_top; } error = ttyld_open(tp, dev); - disc_optim(tp, &tp->t_termios, com); + com->hotchar = ttyldoptim(tp); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) com->active_out = TRUE; siosettimeout(); out: splx(s); if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); return (error); } static int sioclose(dev, flag, mode, td) dev_t dev; int flag; int mode; struct thread *td; { struct com_s *com; int mynor; int s; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (0); com = com_addr(MINOR_TO_UNIT(mynor)); if (com == NULL) return (ENODEV); tp = com->tp; s = spltty(); ttyld_close(tp, flag); #ifdef PC98 com->modem_checking = 0; #endif - disc_optim(tp, &tp->t_termios, com); + com->hotchar = ttyldoptim(tp); comhardclose(com); ttyclose(tp); siosettimeout(); splx(s); if (com->gone) { printf("sio%d: gone\n", com->unit); s = spltty(); if (com->ibuf != NULL) free(com->ibuf, M_DEVBUF); bzero(tp, sizeof *tp); splx(s); } return (0); } static void comhardclose(com) struct com_s *com; { int s; struct tty *tp; s = spltty(); com->poll = FALSE; com->poll_output = FALSE; com->do_timestamp = FALSE; com->do_dcd_timestamp = FALSE; com->pps.ppsparam.mode = 0; #ifdef PC98 if (IS_8251(com->pc98_if_type)) com_send_break_off(com); else #endif sio_setreg(com, com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); tp = com->tp; #if defined(DDB) && (defined(BREAK_TO_DEBUGGER) || \ defined(ALT_BREAK_TO_DEBUGGER)) /* * Leave interrupts enabled and don't clear DTR if this is the * console. This allows us to detect break-to-debugger events * while the console device is closed. */ if (com->unit != comconsole) #endif { #ifdef PC98 int tmp; if (IS_8251(com->pc98_if_type)) com_int_TxRx_disable(com); else sio_setreg(com, com_ier, 0); if (com->pc98_if_type == COM_IF_RSA98III) outb(com->rsabase + rsa_ier, 0x00); if (IS_8251(com->pc98_if_type)) tmp = pc98_get_modem_status(com) & TIOCM_CAR; else tmp = com->prev_modem_status & MSR_DCD; #else sio_setreg(com, com_ier, 0); #endif if (tp->t_cflag & HUPCL /* * XXX we will miss any carrier drop between here and the * next open. Perhaps we should watch DCD even when the * port is closed; it is not sufficient to check it at * the next open because it might go up and down while * we're not watching. */ || (!com->active_out #ifdef PC98 && !(tmp) #else && !(com->prev_modem_status & MSR_DCD) #endif && !(com->it_in.c_cflag & CLOCAL)) || !(tp->t_state & TS_ISOPEN)) { #ifdef PC98 if (IS_8251(com->pc98_if_type)) com_tiocm_bic(com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE); else #endif (void)commctl(com, TIOCM_DTR, DMBIC); if (com->dtr_wait != 0 && !(com->state & CS_DTR_OFF)) { timeout(siodtrwakeup, com, com->dtr_wait); com->state |= CS_DTR_OFF; } } #ifdef PC98 else { if (IS_8251(com->pc98_if_type)) com_tiocm_bic(com, TIOCM_LE); } #endif } #ifdef PC98 if (com->pc98_8251fifo) { if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_XMT_RST | CTRL8251F_RCV_RST); com->pc98_8251fifo_enable = 0; } #endif if (com->hasfifo) { /* * Disable fifos so that they are off after controlled * reboots. Some BIOSes fail to detect 16550s when the * fifos are enabled. */ sio_setreg(com, com_fifo, 0); } com->active_out = FALSE; wakeup(&com->active_out); wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } static int sioread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct com_s *com; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); com = com_addr(MINOR_TO_UNIT(mynor)); if (com == NULL || com->gone) return (ENODEV); return (ttyld_read(com->tp, uio, flag)); } static int siowrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct com_s *com; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); if (com == NULL || com->gone) return (ENODEV); /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that * is not the console. In that situation we don't need/want the X * server taking over the console. */ if (constty != NULL && unit == comconsole) constty = NULL; return (ttyld_write(com->tp, uio, flag)); } static void siobusycheck(chan) void *chan; { struct com_s *com; int s; com = (struct com_s *)chan; /* * Clear TS_BUSY if low-level output is complete. * spl locking is sufficient because siointr1() does not set CS_BUSY. * If siointr1() clears CS_BUSY after we look at it, then we'll get * called again. Reading the line status port outside of siointr1() * is safe because CS_BUSY is clear so there are no output interrupts * to lose. */ s = spltty(); if (com->state & CS_BUSY) com->extra_state &= ~CSE_BUSYCHECK; /* False alarm. */ #ifdef PC98 else if ((IS_8251(com->pc98_if_type) && ((com->pc98_8251fifo_enable && (inb(I8251F_lsr) & (STS8251F_TxRDY | STS8251F_TxEMP)) == (STS8251F_TxRDY | STS8251F_TxEMP)) || (!com->pc98_8251fifo_enable && (inb(com->sts_port) & (STS8251_TxRDY | STS8251_TxEMP)) == (STS8251_TxRDY | STS8251_TxEMP)))) || ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY))) { #else else if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY)) { #endif com->tp->t_state &= ~TS_BUSY; ttwwakeup(com->tp); com->extra_state &= ~CSE_BUSYCHECK; } else timeout(siobusycheck, com, hz / 100); splx(s); } static u_int siodivisor(rclk, speed) u_long rclk; speed_t speed; { long actual_speed; u_int divisor; int error; if (speed == 0) return (0); #if UINT_MAX > (ULONG_MAX - 1) / 8 if (speed > (ULONG_MAX - 1) / 8) return (0); #endif divisor = (rclk / (8UL * speed) + 1) / 2; if (divisor == 0 || divisor >= 65536) return (0); actual_speed = rclk / (16UL * divisor); /* 10 times error in percent: */ error = ((actual_speed - (long)speed) * 2000 / (long)speed + 1) / 2; /* 3.0% maximum error tolerance: */ if (error < -30 || error > 30) return (0); return (divisor); } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } /* * Call this function with the sio_lock mutex held. It will return with the * lock still held. */ static void sioinput(com) struct com_s *com; { u_char *buf; int incc; u_char line_status; int recv_data; struct tty *tp; buf = com->ibuf; tp = com->tp; if (!(tp->t_state & TS_ISOPEN) || !(tp->t_cflag & CREAD)) { com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; return; } if (tp->t_state & TS_CAN_BYPASS_L_RINT) { /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ do { /* * This may look odd, but it is using save-and-enable * semantics instead of the save-and-disable semantics * that are used everywhere else. */ mtx_unlock_spin(&sio_lock); incc = com->iptr - buf; if (tp->t_rawq.c_cc + incc > tp->t_ihiwat && (com->state & CS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); com->delta_error_counts[CE_TTY_BUF_OVERFLOW] += b_to_q((char *)buf, incc, &tp->t_rawq); buf += incc; tk_nin += incc; tk_rawcc += incc; tp->t_rawcc += incc; ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; comstart(tp); } mtx_lock_spin(&sio_lock); } while (buf < com->iptr); } else { do { /* * This may look odd, but it is using save-and-enable * semantics instead of the save-and-disable semantics * that are used everywhere else. */ mtx_unlock_spin(&sio_lock); line_status = buf[com->ierroff]; recv_data = *buf++; if (line_status & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { if (line_status & LSR_BI) recv_data |= TTY_BI; if (line_status & LSR_FE) recv_data |= TTY_FE; if (line_status & LSR_OE) recv_data |= TTY_OE; if (line_status & LSR_PE) recv_data |= TTY_PE; } ttyld_rint(tp, recv_data); mtx_lock_spin(&sio_lock); } while (buf < com->iptr); } com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; /* * There is now room for another low-level buffer full of input, * so enable RTS if it is now disabled and there is room in the * high-level buffer. */ #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if ((com->state & CS_RTS_IFLOW) && !(com_tiocm_get(com) & TIOCM_RTS) && !(tp->t_state & TS_TBLOCK)) com_tiocm_bis(com, TIOCM_RTS); } else { if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } #else if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #endif } static void siointr(arg) void *arg; { struct com_s *com; #if defined(PC98) && defined(COM_MULTIPORT) u_char rsa_buf_status; #endif #ifndef COM_MULTIPORT com = (struct com_s *)arg; mtx_lock_spin(&sio_lock); siointr1(com); mtx_unlock_spin(&sio_lock); #else /* COM_MULTIPORT */ bool_t possibly_more_intrs; int unit; /* * Loop until there is no activity on any port. This is necessary * to get an interrupt edge more than to avoid another interrupt. * If the IRQ signal is just an OR of the IRQ signals from several * devices, then the edge from one may be lost because another is * on. */ mtx_lock_spin(&sio_lock); do { possibly_more_intrs = FALSE; for (unit = 0; unit < sio_numunits; ++unit) { com = com_addr(unit); /* * XXX COM_LOCK(); * would it work here, or be counter-productive? */ #ifdef PC98 if (com != NULL && !com->gone && IS_8251(com->pc98_if_type)) { siointr1(com); } else if (com != NULL && !com->gone && com->pc98_if_type == COM_IF_RSA98III) { rsa_buf_status = inb(com->rsabase + rsa_srr) & 0xc9; if ((rsa_buf_status & 0xc8) || !(rsa_buf_status & 0x01)) { siointr1(com); if (rsa_buf_status != (inb(com->rsabase + rsa_srr) & 0xc9)) possibly_more_intrs = TRUE; } } else #endif if (com != NULL && !com->gone && (inb(com->int_id_port) & IIR_IMASK) != IIR_NOPEND) { siointr1(com); possibly_more_intrs = TRUE; } /* XXX COM_UNLOCK(); */ } } while (possibly_more_intrs); mtx_unlock_spin(&sio_lock); #endif /* COM_MULTIPORT */ } static struct timespec siots[8]; static int siotso; static int volatile siotsunit = -1; static int sysctl_siots(SYSCTL_HANDLER_ARGS) { char buf[128]; long long delta; size_t len; int error, i, tso; for (i = 1, tso = siotso; i < tso; i++) { delta = (long long)(siots[i].tv_sec - siots[i - 1].tv_sec) * 1000000000 + (siots[i].tv_nsec - siots[i - 1].tv_nsec); len = sprintf(buf, "%lld\n", delta); if (delta >= 110000) len += sprintf(buf + len - 1, ": *** %ld.%09ld\n", (long)siots[i].tv_sec, siots[i].tv_nsec) - 1; if (i == tso - 1) buf[len - 1] = '\0'; error = SYSCTL_OUT(req, buf, len); if (error != 0) return (error); uio_yield(); } return (0); } SYSCTL_PROC(_machdep, OID_AUTO, siots, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, sysctl_siots, "A", "sio timestamps"); static void siointr1(com) struct com_s *com; { u_char int_ctl; u_char int_ctl_new; u_char line_status; u_char modem_status; u_char *ioptr; u_char recv_data; #ifdef PC98 u_char tmp = 0; u_char rsa_buf_status = 0; int rsa_tx_fifo_size = 0; #endif /* PC98 */ if (COM_IIR_TXRDYBUG(com->flags)) { int_ctl = inb(com->int_ctl_port); int_ctl_new = int_ctl; } else { int_ctl = 0; int_ctl_new = 0; } while (!com->gone) { #ifdef PC98 status_read:; if (IS_8251(com->pc98_if_type)) { if (com->pc98_8251fifo_enable) tmp = inb(I8251F_lsr); else tmp = inb(com->sts_port); more_intr: line_status = 0; if (com->pc98_8251fifo_enable) { if (tmp & STS8251F_TxRDY) line_status |= LSR_TXRDY; if (tmp & STS8251F_RxRDY) line_status |= LSR_RXRDY; if (tmp & STS8251F_TxEMP) line_status |= LSR_TSRE; if (tmp & STS8251F_PE) line_status |= LSR_PE; if (tmp & STS8251F_OE) line_status |= LSR_OE; if (tmp & STS8251F_BD_SD) line_status |= LSR_BI; } else { if (tmp & STS8251_TxRDY) line_status |= LSR_TXRDY; if (tmp & STS8251_RxRDY) line_status |= LSR_RXRDY; if (tmp & STS8251_TxEMP) line_status |= LSR_TSRE; if (tmp & STS8251_PE) line_status |= LSR_PE; if (tmp & STS8251_OE) line_status |= LSR_OE; if (tmp & STS8251_FE) line_status |= LSR_FE; if (tmp & STS8251_BD_SD) line_status |= LSR_BI; } } else { #endif /* PC98 */ if (com->pps.ppsparam.mode & PPS_CAPTUREBOTH) { modem_status = inb(com->modem_status_port); if ((modem_status ^ com->last_modem_status) & com->pps_bit) { pps_capture(&com->pps); pps_event(&com->pps, (modem_status & com->pps_bit) ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR); } } line_status = inb(com->line_status_port); #ifdef PC98 } if (com->pc98_if_type == COM_IF_RSA98III) rsa_buf_status = inb(com->rsabase + rsa_srr); #endif /* PC98 */ /* input event? (check first to help avoid overruns) */ #ifndef PC98 while (line_status & LSR_RCV_MASK) { #else while ((line_status & LSR_RCV_MASK) || (com->pc98_if_type == COM_IF_RSA98III && (rsa_buf_status & 0x08))) { #endif /* PC98 */ /* break/unnattached error bits or real input? */ #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if (com->pc98_8251fifo_enable) { recv_data = inb(I8251F_data); if (tmp & (STS8251F_PE | STS8251F_OE | STS8251F_BD_SD)) { pc98_i8251_or_cmd(com, CMD8251_ER); recv_data = 0; } } else { recv_data = inb(com->data_port); if (tmp & (STS8251_PE | STS8251_OE | STS8251_FE | STS8251_BD_SD)) { pc98_i8251_or_cmd(com, CMD8251_ER); recv_data = 0; } } } else if (com->pc98_if_type == COM_IF_RSA98III) { if (!(rsa_buf_status & 0x08)) recv_data = 0; else recv_data = inb(com->data_port); } else #endif if (!(line_status & LSR_RXRDY)) recv_data = 0; else recv_data = inb(com->data_port); #ifdef DDB #ifdef ALT_BREAK_TO_DEBUGGER if (com->unit == comconsole && db_alt_break(recv_data, &com->alt_brk_state) != 0) breakpoint(); #endif /* ALT_BREAK_TO_DEBUGGER */ #endif /* DDB */ if (line_status & (LSR_BI | LSR_FE | LSR_PE)) { /* * Don't store BI if IGNBRK or FE/PE if IGNPAR. * Otherwise, push the work to a higher level * (to handle PARMRK) if we're bypassing. * Otherwise, convert BI/FE and PE+INPCK to 0. * * This makes bypassing work right in the * usual "raw" case (IGNBRK set, and IGNPAR * and INPCK clear). * * Note: BI together with FE/PE means just BI. */ if (line_status & LSR_BI) { #if defined(DDB) && defined(BREAK_TO_DEBUGGER) if (com->unit == comconsole) { breakpoint(); goto cont; } #endif if (com->tp == NULL || com->tp->t_iflag & IGNBRK) goto cont; } else { if (com->tp == NULL || com->tp->t_iflag & IGNPAR) goto cont; } if (com->tp->t_state & TS_CAN_BYPASS_L_RINT && (line_status & (LSR_BI | LSR_FE) || com->tp->t_iflag & INPCK)) recv_data = 0; } ++com->bytes_in; if (com->hotchar != 0 && recv_data == com->hotchar) swi_sched(sio_fast_ih, 0); ioptr = com->iptr; if (ioptr >= com->ibufend) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { if (com->do_timestamp) microtime(&com->timestamp); ++com_events; swi_sched(sio_slow_ih, SWI_DELAY); #if 0 /* for testing input latency vs efficiency */ if (com->iptr - com->ibuf == 8) swi_sched(sio_fast_ih, 0); #endif ioptr[0] = recv_data; ioptr[com->ierroff] = line_status; com->iptr = ++ioptr; if (ioptr == com->ihighwater && com->state & CS_RTS_IFLOW) #ifdef PC98 IS_8251(com->pc98_if_type) ? com_tiocm_bic(com, TIOCM_RTS) : #endif outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); if (line_status & LSR_OE) CE_RECORD(com, CE_OVERRUN); } cont: if (line_status & LSR_TXRDY && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) goto txrdy; /* * "& 0x7F" is to avoid the gcc-1.40 generating a slow * jump from the top of the loop to here */ #ifdef PC98 if (IS_8251(com->pc98_if_type)) goto status_read; else #endif line_status = inb(com->line_status_port) & 0x7F; #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) rsa_buf_status = inb(com->rsabase + rsa_srr); #endif /* PC98 */ } /* modem status change? (always check before doing output) */ #ifdef PC98 if (!IS_8251(com->pc98_if_type)) { #endif modem_status = inb(com->modem_status_port); if (modem_status != com->last_modem_status) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * Schedule high level to handle DCD changes. Note * that we don't use the delta bits anywhere. Some * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; swi_sched(sio_fast_ih, 0); } /* handle CTS change immediately for crisp flow ctl */ if (com->state & CS_CTS_OFLOW) { if (modem_status & MSR_CTS) com->state |= CS_ODEVREADY; else com->state &= ~CS_ODEVREADY; } } #ifdef PC98 } #endif txrdy: /* output queued and everything ready? */ #ifndef PC98 if (line_status & LSR_TXRDY && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { #else if (((com->pc98_if_type == COM_IF_RSA98III) ? (rsa_buf_status & 0x02) : (line_status & LSR_TXRDY)) && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { #endif #ifdef PC98 Port_t tmp_data_port; if (IS_8251(com->pc98_if_type) && com->pc98_8251fifo_enable) tmp_data_port = I8251F_data; else tmp_data_port = com->data_port; #endif ioptr = com->obufq.l_head; if (com->tx_fifo_size > 1 && com->unit != siotsunit) { u_int ocount; ocount = com->obufq.l_tail - ioptr; #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) { rsa_buf_status = inb(com->rsabase + rsa_srr); rsa_tx_fifo_size = 1024; if (!(rsa_buf_status & 0x01)) rsa_tx_fifo_size = 2048; if (ocount > rsa_tx_fifo_size) ocount = rsa_tx_fifo_size; } else #endif if (ocount > com->tx_fifo_size) ocount = com->tx_fifo_size; com->bytes_out += ocount; do #ifdef PC98 outb(tmp_data_port, *ioptr++); #else outb(com->data_port, *ioptr++); #endif while (--ocount != 0); } else { #ifdef PC98 outb(tmp_data_port, *ioptr++); #else outb(com->data_port, *ioptr++); #endif ++com->bytes_out; if (com->unit == siotsunit && siotso < sizeof siots / sizeof siots[0]) nanouptime(&siots[siotso++]); } #ifdef PC98 if (IS_8251(com->pc98_if_type)) if (!(pc98_check_i8251_interrupt(com) & IEN_TxFLAG)) com_int_Tx_enable(com); #endif com->obufq.l_head = ioptr; if (COM_IIR_TXRDYBUG(com->flags)) int_ctl_new = int_ctl | IER_ETXRDY; if (ioptr >= com->obufq.l_tail) { struct lbq *qp; qp = com->obufq.l_next; qp->l_queued = FALSE; qp = qp->l_next; if (qp != NULL) { com->obufq.l_head = qp->l_head; com->obufq.l_tail = qp->l_tail; com->obufq.l_next = qp; } else { /* output just completed */ if (COM_IIR_TXRDYBUG(com->flags)) int_ctl_new = int_ctl & ~IER_ETXRDY; com->state &= ~CS_BUSY; #if defined(PC98) if (IS_8251(com->pc98_if_type) && pc98_check_i8251_interrupt(com) & IEN_TxFLAG) com_int_Tx_disable(com); #endif } if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; /* handle at high level ASAP */ swi_sched(sio_fast_ih, 0); } } #ifdef PC98 if (COM_IIR_TXRDYBUG(com->flags) && int_ctl != int_ctl_new) { if (com->pc98_if_type == COM_IF_RSA98III) { int_ctl_new &= ~(IER_ETXRDY | IER_ERXRDY); outb(com->int_ctl_port, int_ctl_new); outb(com->rsabase + rsa_ier, 0x1d); } else outb(com->int_ctl_port, int_ctl_new); } #else if (COM_IIR_TXRDYBUG(com->flags) && int_ctl != int_ctl_new) outb(com->int_ctl_port, int_ctl_new); #endif } #ifdef PC98 else if (line_status & LSR_TXRDY) { if (IS_8251(com->pc98_if_type)) if (pc98_check_i8251_interrupt(com) & IEN_TxFLAG) com_int_Tx_disable(com); } if (IS_8251(com->pc98_if_type)) { if (com->pc98_8251fifo_enable) { if ((tmp = inb(I8251F_lsr)) & STS8251F_RxRDY) goto more_intr; } else { if ((tmp = inb(com->sts_port)) & STS8251_RxRDY) goto more_intr; } } #endif /* finished? */ #ifndef COM_MULTIPORT #ifdef PC98 if (IS_8251(com->pc98_if_type)) return; #endif if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) #endif /* COM_MULTIPORT */ return; } } static int sioioctl(dev, cmd, data, flag, td) dev_t dev; u_long cmd; caddr_t data; int flag; struct thread *td; { struct com_s *com; int error; int mynor; int s; struct tty *tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) u_long oldcmd; struct termios term; #endif mynor = minor(dev); com = com_addr(MINOR_TO_UNIT(mynor)); if (com == NULL || com->gone) return (ENODEV); if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->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); default: return (ENOTTY); } } tp = com->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 *)data; struct termios *lt = mynor & CALLOUT_MASK ? &com->lt_out : &com->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); 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 = ttyioctl(dev, cmd, data, flag, td); - disc_optim(tp, &tp->t_termios, com); + com->hotchar = ttyldoptim(tp); if (error != ENOTTY) return (error); s = spltty(); #ifdef PC98 if (IS_8251(com->pc98_if_type)) { switch (cmd) { case TIOCSBRK: com_send_break_on(com); break; case TIOCCBRK: com_send_break_off(com); break; case TIOCSDTR: com_tiocm_bis(com, TIOCM_DTR | TIOCM_RTS); break; case TIOCCDTR: com_tiocm_bic(com, TIOCM_DTR); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: com_tiocm_set(com, *(int *)data); break; case TIOCMBIS: com_tiocm_bis(com, *(int *)data); break; case TIOCMBIC: com_tiocm_bic(com, *(int *)data); break; case TIOCMGET: *(int *)data = com_tiocm_get(com); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(td); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); error = pps_ioctl(cmd, data, &com->pps); if (error == ENODEV) error = ENOTTY; return (error); } } else { #endif switch (cmd) { case TIOCSBRK: sio_setreg(com, com_cfcr, com->cfcr_image |= CFCR_SBREAK); break; case TIOCCBRK: sio_setreg(com, com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); break; case TIOCSDTR: (void)commctl(com, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void)commctl(com, TIOCM_DTR, DMBIC); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: (void)commctl(com, *(int *)data, DMSET); break; case TIOCMBIS: (void)commctl(com, *(int *)data, DMBIS); break; case TIOCMBIC: (void)commctl(com, *(int *)data, DMBIC); break; case TIOCMGET: *(int *)data = commctl(com, 0, DMGET); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(td); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); error = pps_ioctl(cmd, data, &com->pps); if (error == ENODEV) error = ENOTTY; return (error); } #ifdef PC98 } #endif splx(s); return (0); } /* software interrupt handler for SWI_TTY */ static void siopoll(void *dummy) { int unit; if (com_events == 0) return; repeat: for (unit = 0; unit < sio_numunits; ++unit) { struct com_s *com; int incc; struct tty *tp; com = com_addr(unit); if (com == NULL) continue; tp = com->tp; if (tp == NULL || com->gone) { /* * Discard any events related to never-opened or * going-away devices. */ mtx_lock_spin(&sio_lock); incc = com->iptr - com->ibuf; com->iptr = com->ibuf; if (com->state & CS_CHECKMSR) { incc += LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; } com_events -= incc; mtx_unlock_spin(&sio_lock); continue; } if (com->iptr != com->ibuf) { mtx_lock_spin(&sio_lock); sioinput(com); mtx_unlock_spin(&sio_lock); } if (com->state & CS_CHECKMSR) { u_char delta_modem_status; #ifdef PC98 if (!IS_8251(com->pc98_if_type)) { #endif mtx_lock_spin(&sio_lock); delta_modem_status = com->last_modem_status ^ com->prev_modem_status; com->prev_modem_status = com->last_modem_status; com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; mtx_unlock_spin(&sio_lock); if (delta_modem_status & MSR_DCD) ttyld_modem(tp, com->prev_modem_status & MSR_DCD); #ifdef PC98 } #endif } if (com->state & CS_ODONE) { mtx_lock_spin(&sio_lock); com_events -= LOTS_OF_EVENTS; com->state &= ~CS_ODONE; mtx_unlock_spin(&sio_lock); if (!(com->state & CS_BUSY) && !(com->extra_state & CSE_BUSYCHECK)) { timeout(siobusycheck, com, hz / 100); com->extra_state |= CSE_BUSYCHECK; } ttyld_start(tp); } if (com_events == 0) break; } if (com_events >= LOTS_OF_EVENTS) goto repeat; } static int comparam(tp, t) struct tty *tp; struct termios *t; { u_int cfcr; int cflag; struct com_s *com; u_int divisor; u_char dlbh; u_char dlbl; u_char efr_flowbits; int s; int unit; #ifdef PC98 u_char param = 0; #endif unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); if (com == NULL) return (ENODEV); #ifdef PC98 cfcr = 0; if (IS_8251(com->pc98_if_type)) { if (pc98_ttspeedtab(com, t->c_ospeed, &divisor) != 0) return (EINVAL); } else { #endif /* check requested parameters */ if (t->c_ispeed != (t->c_ospeed != 0 ? t->c_ospeed : tp->t_ospeed)) return (EINVAL); divisor = siodivisor(com->rclk, t->c_ispeed); if (divisor == 0) return (EINVAL); #ifdef PC98 } #endif /* parameters are OK, convert them to the com struct and the device */ s = spltty(); #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if (t->c_ospeed == 0) com_tiocm_bic(com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE); else com_tiocm_bis(com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE); } else #endif if (t->c_ospeed == 0) (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ else (void)commctl(com, TIOCM_DTR, DMBIS); cflag = t->c_cflag; #ifdef PC98 if (!IS_8251(com->pc98_if_type)) { #endif switch (cflag & CSIZE) { case CS5: cfcr = CFCR_5BITS; break; case CS6: cfcr = CFCR_6BITS; break; case CS7: cfcr = CFCR_7BITS; break; default: cfcr = CFCR_8BITS; break; } if (cflag & PARENB) { cfcr |= CFCR_PENAB; if (!(cflag & PARODD)) cfcr |= CFCR_PEVEN; } if (cflag & CSTOPB) cfcr |= CFCR_STOPB; if (com->hasfifo) { /* * Use a fifo trigger level low enough so that the input * latency from the fifo is less than about 16 msec and * the total latency is less than about 30 msec. These * latencies are reasonable for humans. Serial comms * protocols shouldn't expect anything better since modem * latencies are larger. * * The fifo trigger level cannot be set at RX_HIGH for high * speed connections without further work on reducing * interrupt disablement times in other parts of the system, * without producing silo overflow errors. */ com->fifo_image = com->unit == siotsunit ? 0 : t->c_ispeed <= 4800 ? FIFO_ENABLE : FIFO_ENABLE | FIFO_RX_MEDH; #ifdef COM_ESP /* * The Hayes ESP card needs the fifo DMA mode bit set * in compatibility mode. If not, it will interrupt * for each character received. */ if (com->esp) com->fifo_image |= FIFO_DMA_MODE; #endif sio_setreg(com, com_fifo, com->fifo_image); } #ifdef PC98 } #endif /* * This returns with interrupts disabled so that we can complete * the speed change atomically. Keeping interrupts disabled is * especially important while com_data is hidden. */ (void) siosetwater(com, t->c_ispeed); #ifdef PC98 if (IS_8251(com->pc98_if_type)) com_cflag_and_speed_set(com, cflag, t->c_ospeed); else { #endif sio_setreg(com, com_cfcr, cfcr | CFCR_DLAB); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (UMC8669F), setting them while input * is arriving loses sync until data stops arriving. */ dlbl = divisor & 0xFF; if (sio_getreg(com, com_dlbl) != dlbl) sio_setreg(com, com_dlbl, dlbl); dlbh = divisor >> 8; if (sio_getreg(com, com_dlbh) != dlbh) sio_setreg(com, com_dlbh, dlbh); #ifdef PC98 } #endif efr_flowbits = 0; if (cflag & CRTS_IFLOW) { com->state |= CS_RTS_IFLOW; efr_flowbits |= EFR_AUTORTS; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ #ifdef PC98 if (IS_8251(com->pc98_if_type)) com_tiocm_bis(com, TIOCM_RTS); else outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #endif } /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state |= CS_ODEVREADY; com->state &= ~CS_CTS_OFLOW; #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) { param = inb(com->rsabase + rsa_msr); outb(com->rsabase + rsa_msr, param & 0x14); } #endif if (cflag & CCTS_OFLOW) { com->state |= CS_CTS_OFLOW; efr_flowbits |= EFR_AUTOCTS; #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if (!(pc98_get_modem_status(com) & TIOCM_CTS)) com->state &= ~CS_ODEVREADY; } else if (com->pc98_if_type == COM_IF_RSA98III) { /* Set automatic flow control mode */ outb(com->rsabase + rsa_msr, param | 0x08); } else #endif if (!(com->last_modem_status & MSR_CTS)) com->state &= ~CS_ODEVREADY; } #ifdef PC98 if (!IS_8251(com->pc98_if_type)) sio_setreg(com, com_cfcr, com->cfcr_image = cfcr); #else if (com->st16650a) { sio_setreg(com, com_lcr, LCR_EFR_ENABLE); sio_setreg(com, com_efr, (sio_getreg(com, com_efr) & ~(EFR_AUTOCTS | EFR_AUTORTS)) | efr_flowbits); } sio_setreg(com, com_cfcr, com->cfcr_image = cfcr); #endif /* XXX shouldn't call functions while intrs are disabled. */ - disc_optim(tp, t, com); + com->hotchar = ttyldoptim(tp); mtx_unlock_spin(&sio_lock); splx(s); comstart(tp); if (com->ibufold != NULL) { free(com->ibufold, M_DEVBUF); com->ibufold = NULL; } return (0); } /* * This function must be called with the sio_lock mutex released and will * return with it obtained. */ static int siosetwater(com, speed) struct com_s *com; speed_t speed; { int cp4ticks; u_char *ibuf; int ibufsize; struct tty *tp; /* * Make the buffer size large enough to handle a softtty interrupt * latency of about 2 ticks without loss of throughput or data * (about 3 ticks if input flow control is not used or not honoured, * but a bit less for CS5-CS7 modes). */ cp4ticks = speed / 10 / hz * 4; for (ibufsize = 128; ibufsize < cp4ticks;) ibufsize <<= 1; #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) ibufsize = 2048; #endif if (ibufsize == com->ibufsize) { mtx_lock_spin(&sio_lock); return (0); } /* * Allocate input buffer. The extra factor of 2 in the size is * to allow for an error byte for each input byte. */ ibuf = malloc(2 * ibufsize, M_DEVBUF, M_NOWAIT); if (ibuf == NULL) { mtx_lock_spin(&sio_lock); return (ENOMEM); } /* Initialize non-critical variables. */ com->ibufold = com->ibuf; com->ibufsize = ibufsize; tp = com->tp; if (tp != NULL) { tp->t_ififosize = 2 * ibufsize; tp->t_ispeedwat = (speed_t)-1; tp->t_ospeedwat = (speed_t)-1; } /* * Read current input buffer, if any. Continue with interrupts * disabled. */ mtx_lock_spin(&sio_lock); if (com->iptr != com->ibuf) sioinput(com); /*- * Initialize critical variables, including input buffer watermarks. * The external device is asked to stop sending when the buffer * exactly reaches high water, or when the high level requests it. * The high level is notified immediately (rather than at a later * clock tick) when this watermark is reached. * The buffer size is chosen so the watermark should almost never * be reached. * The low watermark is invisibly 0 since the buffer is always * emptied all at once. */ com->iptr = com->ibuf = ibuf; com->ibufend = ibuf + ibufsize; com->ierroff = ibufsize; com->ihighwater = ibuf + 3 * ibufsize / 4; return (0); } static void comstart(tp) struct tty *tp; { struct com_s *com; int s; int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); if (com == NULL) return; s = spltty(); mtx_lock_spin(&sio_lock); if (tp->t_state & TS_TTSTOP) com->state &= ~CS_TTGO; else com->state |= CS_TTGO; if (tp->t_state & TS_TBLOCK) { #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if ((com_tiocm_get(com) & TIOCM_RTS) && (com->state & CS_RTS_IFLOW)) com_tiocm_bic(com, TIOCM_RTS); } else { if ((com->mcr_image & MCR_RTS) && (com->state & CS_RTS_IFLOW)) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); } #else if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #endif } else { #ifdef PC98 if (IS_8251(com->pc98_if_type)) { if (!(com_tiocm_get(com) & TIOCM_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) com_tiocm_bis(com, TIOCM_RTS); } else { if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } #else if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #endif } mtx_unlock_spin(&sio_lock); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(s); return; } if (tp->t_outq.c_cc != 0) { struct lbq *qp; struct lbq *next; if (!com->obufs[0].l_queued) { com->obufs[0].l_tail = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, #ifdef PC98 com->obufsize); #else sizeof com->obuf1); #endif com->obufs[0].l_next = NULL; com->obufs[0].l_queued = TRUE; mtx_lock_spin(&sio_lock); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[0]; } else { com->obufq.l_head = com->obufs[0].l_head; com->obufq.l_tail = com->obufs[0].l_tail; com->obufq.l_next = &com->obufs[0]; com->state |= CS_BUSY; } mtx_unlock_spin(&sio_lock); } if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { com->obufs[1].l_tail = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, #ifdef PC98 com->obufsize); #else sizeof com->obuf2); #endif com->obufs[1].l_next = NULL; com->obufs[1].l_queued = TRUE; mtx_lock_spin(&sio_lock); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[1]; } else { com->obufq.l_head = com->obufs[1].l_head; com->obufq.l_tail = com->obufs[1].l_tail; com->obufq.l_next = &com->obufs[1]; com->state |= CS_BUSY; } mtx_unlock_spin(&sio_lock); } tp->t_state |= TS_BUSY; } mtx_lock_spin(&sio_lock); if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); /* fake interrupt to start output */ mtx_unlock_spin(&sio_lock); ttwwakeup(tp); splx(s); } static void comstop(tp, rw) struct tty *tp; int rw; { struct com_s *com; #ifdef PC98 int rsa98_tmp = 0; #endif com = com_addr(DEV_TO_UNIT(tp->t_dev)); if (com == NULL || com->gone) return; mtx_lock_spin(&sio_lock); if (rw & FWRITE) { #ifdef PC98 if (!IS_8251(com->pc98_if_type)) { #endif if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif sio_setreg(com, com_fifo, FIFO_XMT_RST | com->fifo_image); #ifdef PC98 if (com->pc98_if_type == COM_IF_RSA98III) for (rsa98_tmp = 0; rsa98_tmp < 2048; rsa98_tmp++) sio_setreg(com, com_fifo, FIFO_XMT_RST | com->fifo_image); } #endif com->obufs[0].l_queued = FALSE; com->obufs[1].l_queued = FALSE; if (com->state & CS_ODONE) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); com->tp->t_state &= ~TS_BUSY; } if (rw & FREAD) { #ifdef PC98 if (!IS_8251(com->pc98_if_type)) { if (com->pc98_if_type == COM_IF_RSA98III) for (rsa98_tmp = 0; rsa98_tmp < 2048; rsa98_tmp++) sio_getreg(com, com_data); #endif if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif sio_setreg(com, com_fifo, FIFO_RCV_RST | com->fifo_image); #ifdef PC98 } #endif com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } mtx_unlock_spin(&sio_lock); comstart(tp); } static int commctl(com, bits, how) struct com_s *com; int bits; int how; { int mcr; int msr; if (how == DMGET) { bits = TIOCM_LE; /* XXX - always enabled while open */ mcr = com->mcr_image; if (mcr & MCR_DTR) bits |= TIOCM_DTR; if (mcr & MCR_RTS) bits |= TIOCM_RTS; msr = com->prev_modem_status; if (msr & MSR_CTS) bits |= TIOCM_CTS; if (msr & MSR_DCD) bits |= TIOCM_CD; if (msr & MSR_DSR) bits |= TIOCM_DSR; /* * XXX - MSR_RI is naturally volatile, and we make MSR_TERI * more volatile by reading the modem status a lot. Perhaps * we should latch both bits until the status is read here. */ if (msr & (MSR_RI | MSR_TERI)) bits |= TIOCM_RI; return (bits); } mcr = 0; if (bits & TIOCM_DTR) mcr |= MCR_DTR; if (bits & TIOCM_RTS) mcr |= MCR_RTS; if (com->gone) return(0); mtx_lock_spin(&sio_lock); switch (how) { case DMSET: outb(com->modem_ctl_port, com->mcr_image = mcr | (com->mcr_image & MCR_IENABLE)); break; case DMBIS: outb(com->modem_ctl_port, com->mcr_image |= mcr); break; case DMBIC: outb(com->modem_ctl_port, com->mcr_image &= ~mcr); break; } mtx_unlock_spin(&sio_lock); return (0); } static void siosettimeout() { struct com_s *com; bool_t someopen; int unit; /* * Set our timeout period to 1 second if no polled devices are open. * Otherwise set it to max(1/200, 1/hz). * Enable timeouts iff some device is open. */ untimeout(comwakeup, (void *)NULL, sio_timeout_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < sio_numunits; ++unit) { com = com_addr(unit); if (com != NULL && com->tp != NULL && com->tp->t_state & TS_ISOPEN && !com->gone) { someopen = TRUE; if (com->poll || com->poll_output) { sio_timeout = hz > 200 ? hz / 200 : 1; break; } } } if (someopen) { sio_timeouts_until_log = hz / sio_timeout; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < sio_numunits; ++unit) { com = com_addr(unit); if (com != NULL && !com->gone && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { mtx_lock_spin(&sio_lock); siointr1(com); mtx_unlock_spin(&sio_lock); } } /* * Check for and log errors, but not too often. */ if (--sio_timeouts_until_log > 0) return; sio_timeouts_until_log = hz / sio_timeout; for (unit = 0; unit < sio_numunits; ++unit) { int errnum; com = com_addr(unit); if (com == NULL) continue; if (com->gone) continue; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { u_int delta; u_long total; mtx_lock_spin(&sio_lock); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; mtx_unlock_spin(&sio_lock); if (delta == 0) continue; total = com->error_counts[errnum] += delta; log(LOG_ERR, "sio%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); } } } #ifdef PC98 /* commint is called when modem control line changes */ static void commint(dev_t dev) { register struct tty *tp; int stat,delta; struct com_s *com; int mynor,unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); tp = com->tp; stat = com_tiocm_get(com); delta = com_tiocm_get_delta(com); if (com->state & CS_CTS_OFLOW) { if (stat & TIOCM_CTS) com->state |= CS_ODEVREADY; else com->state &= ~CS_ODEVREADY; } if ((delta & TIOCM_CAR) && (mynor & CALLOUT_MASK) == 0) { if (stat & TIOCM_CAR ) (void)ttyld_modem(tp, 1); else if (ttyld_modem(tp, 0) == 0) { /* negate DTR, RTS */ com_tiocm_bic(com, (tp->t_cflag & HUPCL) ? TIOCM_DTR|TIOCM_RTS|TIOCM_LE : TIOCM_LE ); /* disable IENABLE */ com_int_TxRx_disable( com ); } } } #endif -static void -disc_optim(tp, t, com) - struct tty *tp; - struct termios *t; - struct com_s *com; -{ - if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) - && (!(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; - com->hotchar = linesw[tp->t_line].l_hotchar; -} - /* * Following are all routines needed for SIO to act as console */ struct siocnstate { u_char dlbl; u_char dlbh; u_char ier; u_char cfcr; u_char mcr; }; /* * This is a function in order to not replicate "ttyd%d" more * places than absolutely necessary. */ static void siocnset(struct consdev *cd, int unit) { cd->cn_unit = unit; sprintf(cd->cn_name, "ttyd%d", unit); } #ifndef __alpha__ static speed_t siocngetspeed(Port_t, u_long rclk); #endif static void siocnclose(struct siocnstate *sp, Port_t iobase); static void siocnopen(struct siocnstate *sp, Port_t iobase, int speed); static void siocntxwait(Port_t iobase); #ifdef __alpha__ int siocnattach(int port, int speed); int siogdbattach(int port, int speed); int siogdbgetc(void); void siogdbputc(int c); #else static cn_probe_t siocnprobe; static cn_init_t siocninit; static cn_term_t siocnterm; #endif static cn_checkc_t siocncheckc; static cn_getc_t siocngetc; static cn_putc_t siocnputc; #ifndef __alpha__ CONS_DRIVER(sio, siocnprobe, siocninit, siocnterm, siocngetc, siocncheckc, siocnputc, NULL); #endif #if DDB > 0 static struct consdev gdbconsdev; #endif static void siocntxwait(iobase) Port_t iobase; { int timo; /* * Wait for any pending transmission to finish. Required to avoid * the UART lockup bug when the speed is changed, and for normal * transmits. */ timo = 100000; while ((inb(iobase + com_lsr) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY) && --timo != 0) ; } #ifndef __alpha__ /* * Read the serial port specified and try to figure out what speed * it's currently running at. We're assuming the serial port has * been initialized and is basicly idle. This routine is only intended * to be run at system startup. * * If the value read from the serial port doesn't make sense, return 0. */ static speed_t siocngetspeed(iobase, rclk) Port_t iobase; u_long rclk; { u_int divisor; u_char dlbh; u_char dlbl; u_char cfcr; cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); dlbl = inb(iobase + com_dlbl); dlbh = inb(iobase + com_dlbh); outb(iobase + com_cfcr, cfcr); divisor = dlbh << 8 | dlbl; /* XXX there should be more sanity checking. */ if (divisor == 0) return (CONSPEED); return (rclk / (16UL * divisor)); } #endif static void siocnopen(sp, iobase, speed) struct siocnstate *sp; Port_t iobase; int speed; { u_int divisor; u_char dlbh; u_char dlbl; /* * Save all the device control registers except the fifo register * and set our default ones (cs8 -parenb speed=comdefaultrate). * We can't save the fifo register since it is read-only. */ sp->ier = inb(iobase + com_ier); outb(iobase + com_ier, 0); /* spltty() doesn't stop siointr() */ siocntxwait(iobase); sp->cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); sp->dlbl = inb(iobase + com_dlbl); sp->dlbh = inb(iobase + com_dlbh); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (Startech), setting them clears the * data input register. This also reduces the effects of the * UMC8669F bug. */ divisor = siodivisor(comdefaultrclk, speed); dlbl = divisor & 0xFF; if (sp->dlbl != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = divisor >> 8; if (sp->dlbh != dlbh) outb(iobase + com_dlbh, dlbh); outb(iobase + com_cfcr, CFCR_8BITS); sp->mcr = inb(iobase + com_mcr); /* * We don't want interrupts, but must be careful not to "disable" * them by clearing the MCR_IENABLE bit, since that might cause * an interrupt by floating the IRQ line. */ outb(iobase + com_mcr, (sp->mcr & MCR_IENABLE) | MCR_DTR | MCR_RTS); } static void siocnclose(sp, iobase) struct siocnstate *sp; Port_t iobase; { /* * Restore the device control registers. */ siocntxwait(iobase); outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); if (sp->dlbl != inb(iobase + com_dlbl)) outb(iobase + com_dlbl, sp->dlbl); if (sp->dlbh != inb(iobase + com_dlbh)) outb(iobase + com_dlbh, sp->dlbh); outb(iobase + com_cfcr, sp->cfcr); /* * XXX damp oscillations of MCR_DTR and MCR_RTS by not restoring them. */ outb(iobase + com_mcr, sp->mcr | MCR_DTR | MCR_RTS); outb(iobase + com_ier, sp->ier); } #ifndef __alpha__ static void siocnprobe(cp) struct consdev *cp; { speed_t boot_speed; u_char cfcr; u_int divisor; int s, unit; struct siocnstate sp; /* * Find our first enabled console, if any. If it is a high-level * console device, then initialize it and return successfully. * If it is a low-level console device, then initialize it and * return unsuccessfully. It must be initialized in both cases * for early use by console drivers and debuggers. Initializing * the hardware is not necessary in all cases, since the i/o * routines initialize it on the fly, but it is necessary if * input might arrive while the hardware is switched back to an * uninitialized state. We can't handle multiple console devices * yet because our low-level routines don't take a device arg. * We trust the user to set the console flags properly so that we * don't need to probe. */ cp->cn_pri = CN_DEAD; for (unit = 0; unit < 16; unit++) { /* XXX need to know how many */ int flags; if (resource_disabled("sio", unit)) continue; if (resource_int_value("sio", unit, "flags", &flags)) continue; if (COM_CONSOLE(flags) || COM_DEBUGGER(flags)) { int port; Port_t iobase; if (resource_int_value("sio", unit, "port", &port)) continue; iobase = port; s = spltty(); if (boothowto & RB_SERIAL) { boot_speed = siocngetspeed(iobase, comdefaultrclk); if (boot_speed) comdefaultrate = boot_speed; } /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); divisor = siodivisor(comdefaultrclk, comdefaultrate); outb(iobase + com_dlbl, divisor & 0xff); outb(iobase + com_dlbh, divisor >> 8); outb(iobase + com_cfcr, cfcr); siocnopen(&sp, iobase, comdefaultrate); splx(s); if (COM_CONSOLE(flags) && !COM_LLCONSOLE(flags)) { siocnset(cp, unit); cp->cn_pri = COM_FORCECONSOLE(flags) || boothowto & RB_SERIAL ? CN_REMOTE : CN_NORMAL; siocniobase = iobase; siocnunit = unit; } if (COM_DEBUGGER(flags)) { printf("sio%d: gdb debugging port\n", unit); siogdbiobase = iobase; siogdbunit = unit; #if DDB > 0 siocnset(&gdbconsdev, unit); gdb_arg = &gdbconsdev; gdb_getc = siocngetc; gdb_putc = siocnputc; #endif } } } #ifdef __i386__ #if DDB > 0 /* * XXX Ugly Compatability. * If no gdb port has been specified, set it to be the console * as some configuration files don't specify the gdb port. */ if (gdb_arg == NULL && (boothowto & RB_GDB)) { printf("Warning: no GDB port specified. Defaulting to sio%d.\n", siocnunit); printf("Set flag 0x80 on desired GDB port in your\n"); printf("configuration file (currently sio only).\n"); siogdbiobase = siocniobase; siogdbunit = siocnunit; siocnset(&gdbconsdev, siocnunit); gdb_arg = &gdbconsdev; gdb_getc = siocngetc; gdb_putc = siocnputc; } #endif #endif } static void siocninit(cp) struct consdev *cp; { comconsole = cp->cn_unit; } static void siocnterm(cp) struct consdev *cp; { comconsole = -1; } #endif #ifdef __alpha__ CONS_DRIVER(sio, NULL, NULL, NULL, siocngetc, siocncheckc, siocnputc, NULL); int siocnattach(port, speed) int port; int speed; { int s; u_char cfcr; u_int divisor; struct siocnstate sp; int unit = 0; /* XXX random value! */ siocniobase = port; siocnunit = unit; comdefaultrate = speed; sio_consdev.cn_pri = CN_NORMAL; siocnset(&sio_consdev, unit); s = spltty(); /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(siocniobase + com_cfcr); outb(siocniobase + com_cfcr, CFCR_DLAB | cfcr); divisor = siodivisor(comdefaultrclk, comdefaultrate); outb(siocniobase + com_dlbl, divisor & 0xff); outb(siocniobase + com_dlbh, divisor >> 8); outb(siocniobase + com_cfcr, cfcr); siocnopen(&sp, siocniobase, comdefaultrate); splx(s); cnadd(&sio_consdev); return (0); } int siogdbattach(port, speed) int port; int speed; { int s; u_char cfcr; u_int divisor; struct siocnstate sp; int unit = 1; /* XXX random value! */ siogdbiobase = port; gdbdefaultrate = speed; printf("sio%d: gdb debugging port\n", unit); siogdbunit = unit; #if DDB > 0 siocnset(&gdbconsdev, unit); gdb_arg = &gdbconsdev; gdb_getc = siocngetc; gdb_putc = siocnputc; #endif s = spltty(); /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(siogdbiobase + com_cfcr); outb(siogdbiobase + com_cfcr, CFCR_DLAB | cfcr); divisor = siodivisor(comdefaultrclk, gdbdefaultrate); outb(siogdbiobase + com_dlbl, divisor & 0xff); outb(siogdbiobase + com_dlbh, divisor >> 8); outb(siogdbiobase + com_cfcr, cfcr); siocnopen(&sp, siogdbiobase, gdbdefaultrate); splx(s); return (0); } #endif static int siocncheckc(struct consdev *cd) { int c; Port_t iobase; int s; struct siocnstate sp; speed_t speed; if (cd->cn_unit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); if (inb(iobase + com_lsr) & LSR_RXRDY) c = inb(iobase + com_data); else c = -1; siocnclose(&sp, iobase); splx(s); return (c); } static int siocngetc(struct consdev *cd) { int c; Port_t iobase; int s; struct siocnstate sp; speed_t speed; if (cd->cn_unit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); while (!(inb(iobase + com_lsr) & LSR_RXRDY)) ; c = inb(iobase + com_data); siocnclose(&sp, iobase); splx(s); return (c); } static void siocnputc(struct consdev *cd, int c) { int need_unlock; int s; struct siocnstate sp; Port_t iobase; speed_t speed; if (cd->cn_unit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); need_unlock = 0; if (sio_inited == 2 && !mtx_owned(&sio_lock)) { mtx_lock_spin(&sio_lock); need_unlock = 1; } siocnopen(&sp, iobase, speed); siocntxwait(iobase); outb(iobase + com_data, c); siocnclose(&sp, iobase); if (need_unlock) mtx_unlock_spin(&sio_lock); splx(s); } #ifdef __alpha__ int siogdbgetc() { int c; Port_t iobase; speed_t speed; int s; struct siocnstate sp; if (siogdbunit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); while (!(inb(iobase + com_lsr) & LSR_RXRDY)) ; c = inb(iobase + com_data); siocnclose(&sp, iobase); splx(s); return (c); } void siogdbputc(c) int c; { Port_t iobase; speed_t speed; int s; struct siocnstate sp; if (siogdbunit == siocnunit) { iobase = siocniobase; speed = comdefaultrate; } else { iobase = siogdbiobase; speed = gdbdefaultrate; } s = spltty(); siocnopen(&sp, iobase, speed); siocntxwait(siogdbiobase); outb(siogdbiobase + com_data, c); siocnclose(&sp, siogdbiobase); splx(s); } #endif #ifdef PC98 /* * pc98 local function */ static void com_tiocm_set(struct com_s *com, int msr) { int s; int tmp = 0; int mask = CMD8251_TxEN|CMD8251_RxEN|CMD8251_DTR|CMD8251_RTS; s=spltty(); com->pc98_prev_modem_status = ( msr & (TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ) | ( com->pc98_prev_modem_status & ~(TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ); tmp |= (CMD8251_TxEN|CMD8251_RxEN); if ( msr & TIOCM_DTR ) tmp |= CMD8251_DTR; if ( msr & TIOCM_RTS ) tmp |= CMD8251_RTS; pc98_i8251_clear_or_cmd( com, mask, tmp ); splx(s); } static void com_tiocm_bis(struct com_s *com, int msr) { int s; int tmp = 0; s=spltty(); com->pc98_prev_modem_status |= ( msr & (TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ); tmp |= CMD8251_TxEN|CMD8251_RxEN; if ( msr & TIOCM_DTR ) tmp |= CMD8251_DTR; if ( msr & TIOCM_RTS ) tmp |= CMD8251_RTS; pc98_i8251_or_cmd( com, tmp ); splx(s); } static void com_tiocm_bic(struct com_s *com, int msr) { int s; int tmp = msr; s=spltty(); com->pc98_prev_modem_status &= ~( msr & (TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ); if ( msr & TIOCM_DTR ) tmp |= CMD8251_DTR; if ( msr & TIOCM_RTS ) tmp |= CMD8251_RTS; pc98_i8251_clear_cmd( com, tmp ); splx(s); } static int com_tiocm_get(struct com_s *com) { return( com->pc98_prev_modem_status ); } static int com_tiocm_get_delta(struct com_s *com) { int tmp; tmp = com->pc98_modem_delta; com->pc98_modem_delta = 0; return( tmp ); } /* convert to TIOCM_?? ( ioctl.h ) */ static int pc98_get_modem_status(struct com_s *com) { register int msr; msr = com->pc98_prev_modem_status & ~(TIOCM_CAR|TIOCM_RI|TIOCM_DSR|TIOCM_CTS); if (com->pc98_8251fifo_enable) { int stat2; stat2 = inb(I8251F_msr); if ( stat2 & CICSCDF_CD ) msr |= TIOCM_CAR; if ( stat2 & CICSCDF_CI ) msr |= TIOCM_RI; if ( stat2 & CICSCDF_DR ) msr |= TIOCM_DSR; if ( stat2 & CICSCDF_CS ) msr |= TIOCM_CTS; #if COM_CARRIER_DETECT_EMULATE if ( msr & (TIOCM_DSR|TIOCM_CTS) ) { msr |= TIOCM_CAR; } #endif } else { int stat, stat2; stat = inb(com->sts_port); stat2 = inb(com->in_modem_port); if ( !(stat2 & CICSCD_CD) ) msr |= TIOCM_CAR; if ( !(stat2 & CICSCD_CI) ) msr |= TIOCM_RI; if ( stat & STS8251_DSR ) msr |= TIOCM_DSR; if ( !(stat2 & CICSCD_CS) ) msr |= TIOCM_CTS; #if COM_CARRIER_DETECT_EMULATE if ( msr & (TIOCM_DSR|TIOCM_CTS) ) { msr |= TIOCM_CAR; } #endif } return(msr); } static void pc98_check_msr(void* chan) { int msr, delta; int s; register struct tty *tp; struct com_s *com; int mynor; int unit; dev_t dev; dev=(dev_t)chan; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); tp = com->tp; s = spltty(); msr = pc98_get_modem_status(com); /* make change flag */ delta = msr ^ com->pc98_prev_modem_status; if ( delta & TIOCM_CAR ) { if ( com->modem_car_chg_timer ) { if ( -- com->modem_car_chg_timer ) msr ^= TIOCM_CAR; } else { if ((com->modem_car_chg_timer = (msr & TIOCM_CAR) ? DCD_ON_RECOGNITION : DCD_OFF_TOLERANCE) != 0) msr ^= TIOCM_CAR; } } else com->modem_car_chg_timer = 0; delta = ( msr ^ com->pc98_prev_modem_status ) & (TIOCM_CAR|TIOCM_RI|TIOCM_DSR|TIOCM_CTS); com->pc98_prev_modem_status = msr; delta = ( com->pc98_modem_delta |= delta ); splx(s); if ( com->modem_checking || (tp->t_state & (TS_ISOPEN)) ) { if ( delta ) { commint(dev); } timeout(pc98_check_msr, (caddr_t)dev, PC98_CHECK_MODEM_INTERVAL); } else { com->modem_checking = 0; } } static void pc98_msrint_start(dev_t dev) { struct com_s *com; int mynor; int unit; int s = spltty(); mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); com = com_addr(unit); /* modem control line check routine envoke interval is 1/10 sec */ if ( com->modem_checking == 0 ) { com->pc98_prev_modem_status = pc98_get_modem_status(com); com->pc98_modem_delta = 0; timeout(pc98_check_msr, (caddr_t)dev, PC98_CHECK_MODEM_INTERVAL); com->modem_checking = 1; } splx(s); } static void pc98_disable_i8251_interrupt(struct com_s *com, int mod) { /* disable interrupt */ register int tmp; mod |= ~(IEN_Tx|IEN_TxEMP|IEN_Rx); COM_INT_DISABLE tmp = inb( com->intr_ctrl_port ) & ~(IEN_Tx|IEN_TxEMP|IEN_Rx); outb( com->intr_ctrl_port, (com->intr_enable&=~mod) | tmp ); COM_INT_ENABLE } static void pc98_enable_i8251_interrupt(struct com_s *com, int mod) { register int tmp; COM_INT_DISABLE tmp = inb( com->intr_ctrl_port ) & ~(IEN_Tx|IEN_TxEMP|IEN_Rx); outb( com->intr_ctrl_port, (com->intr_enable|=mod) | tmp ); COM_INT_ENABLE } static int pc98_check_i8251_interrupt(struct com_s *com) { return ( com->intr_enable & 0x07 ); } static void pc98_i8251_clear_cmd(struct com_s *com, int x) { int tmp; COM_INT_DISABLE tmp = com->pc98_prev_siocmd & ~(x); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, 0); outb(com->cmd_port, tmp); com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_ENABLE); COM_INT_ENABLE } static void pc98_i8251_or_cmd(struct com_s *com, int x) { int tmp; COM_INT_DISABLE if (com->pc98_8251fifo_enable) outb(I8251F_fcr, 0); tmp = com->pc98_prev_siocmd | (x); outb(com->cmd_port, tmp); com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_ENABLE); COM_INT_ENABLE } static void pc98_i8251_set_cmd(struct com_s *com, int x) { int tmp; COM_INT_DISABLE if (com->pc98_8251fifo_enable) outb(I8251F_fcr, 0); tmp = (x); outb(com->cmd_port, tmp); com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_ENABLE); COM_INT_ENABLE } static void pc98_i8251_clear_or_cmd(struct com_s *com, int clr, int x) { int tmp; COM_INT_DISABLE if (com->pc98_8251fifo_enable) outb(I8251F_fcr, 0); tmp = com->pc98_prev_siocmd & ~(clr); tmp |= (x); outb(com->cmd_port, tmp); com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_ENABLE); COM_INT_ENABLE } static int pc98_i8251_get_cmd(struct com_s *com) { return com->pc98_prev_siocmd; } static int pc98_i8251_get_mod(struct com_s *com) { return com->pc98_prev_siomod; } static void pc98_i8251_reset(struct com_s *com, int mode, int command) { if (com->pc98_8251fifo_enable) outb(I8251F_fcr, 0); outb(com->cmd_port, 0); /* dummy */ DELAY(2); outb(com->cmd_port, 0); /* dummy */ DELAY(2); outb(com->cmd_port, 0); /* dummy */ DELAY(2); outb(com->cmd_port, CMD8251_RESET); /* internal reset */ DELAY(2); outb(com->cmd_port, mode ); /* mode register */ com->pc98_prev_siomod = mode; DELAY(2); pc98_i8251_set_cmd( com, (command|CMD8251_ER) ); DELAY(10); if (com->pc98_8251fifo_enable) outb(I8251F_fcr, CTRL8251F_ENABLE | CTRL8251F_XMT_RST | CTRL8251F_RCV_RST); } static void pc98_check_sysclock(void) { /* get system clock from port */ if ( pc98_machine_type & M_8M ) { /* 8 MHz system & H98 */ sysclock = 8; } else { /* 5 MHz system */ sysclock = 5; } } static void com_cflag_and_speed_set( struct com_s *com, int cflag, int speed) { int cfcr=0; int previnterrupt; u_int count; if (pc98_ttspeedtab(com, speed, &count) != 0) return; previnterrupt = pc98_check_i8251_interrupt(com); pc98_disable_i8251_interrupt( com, IEN_Tx|IEN_TxEMP|IEN_Rx ); switch ( cflag&CSIZE ) { case CS5: cfcr = MOD8251_5BITS; break; case CS6: cfcr = MOD8251_6BITS; break; case CS7: cfcr = MOD8251_7BITS; break; case CS8: cfcr = MOD8251_8BITS; break; } if ( cflag&PARENB ) { if ( cflag&PARODD ) cfcr |= MOD8251_PODD; else cfcr |= MOD8251_PEVEN; } else cfcr |= MOD8251_PDISAB; if ( cflag&CSTOPB ) cfcr |= MOD8251_STOP2; else cfcr |= MOD8251_STOP1; if ( count & 0x10000 ) cfcr |= MOD8251_CLKX1; else cfcr |= MOD8251_CLKX16; if (epson_machine_id != 0x20) { /* XXX */ int tmp; while (!((tmp = inb(com->sts_port)) & STS8251_TxEMP)) ; } /* set baud rate from ospeed */ pc98_set_baud_rate( com, count ); if ( cfcr != pc98_i8251_get_mod(com) ) pc98_i8251_reset(com, cfcr, pc98_i8251_get_cmd(com) ); pc98_enable_i8251_interrupt( com, previnterrupt ); } static int pc98_ttspeedtab(struct com_s *com, int speed, u_int *divisor) { int if_type, effect_sp, count = -1, mod; if_type = com->pc98_if_type & 0x0f; switch (com->pc98_if_type) { case COM_IF_INTERNAL: if (PC98SIO_baud_rate_port(if_type) != -1) { count = ttspeedtab(speed, if_8251_type[if_type].speedtab); if (count > 0) { count |= COM1_EXT_CLOCK; break; } } /* for *1CLK asynchronous! mode, TEFUTEFU */ mod = (sysclock == 5) ? 2457600 : 1996800; effect_sp = ttspeedtab( speed, pc98speedtab ); if ( effect_sp < 0 ) /* XXX */ effect_sp = ttspeedtab( (speed - 1), pc98speedtab ); if ( effect_sp <= 0 ) return effect_sp; if ( effect_sp == speed ) mod /= 16; if ( mod % effect_sp ) return(-1); count = mod / effect_sp; if ( count > 65535 ) return(-1); if ( effect_sp != speed ) count |= 0x10000; break; case COM_IF_PC9861K_1: case COM_IF_PC9861K_2: count = 1; break; case COM_IF_IND_SS_1: case COM_IF_IND_SS_2: case COM_IF_PIO9032B_1: case COM_IF_PIO9032B_2: count = ttspeedtab( speed, if_8251_type[if_type].speedtab ); break; case COM_IF_B98_01_1: case COM_IF_B98_01_2: count = ttspeedtab( speed, if_8251_type[if_type].speedtab ); #ifdef B98_01_OLD if (count == 0 || count == 1) { count += 4; count |= 0x20000; /* x1 mode for 76800 and 153600 */ } #endif break; } if (count < 0) return count; *divisor = (u_int) count; return 0; } static void pc98_set_baud_rate( struct com_s *com, u_int count ) { int if_type, io, s; if_type = com->pc98_if_type & 0x0f; io = rman_get_start(com->ioportres) & 0xff00; switch (com->pc98_if_type) { case COM_IF_INTERNAL: if (PC98SIO_baud_rate_port(if_type) != -1) { if (count & COM1_EXT_CLOCK) { outb((Port_t)PC98SIO_baud_rate_port(if_type), count & 0xff); break; } else { outb((Port_t)PC98SIO_baud_rate_port(if_type), 0x09); } } if (count == 0) return; /* set i8253 */ s = splclock(); if (count != 3) outb( 0x77, 0xb6 ); else outb( 0x77, 0xb4 ); outb( 0x5f, 0); outb( 0x75, count & 0xff ); outb( 0x5f, 0); outb( 0x75, (count >> 8) & 0xff ); splx(s); break; case COM_IF_IND_SS_1: case COM_IF_IND_SS_2: outb(io | PC98SIO_intr_ctrl_port(if_type), 0); outb(io | PC98SIO_baud_rate_port(if_type), 0); outb(io | PC98SIO_baud_rate_port(if_type), 0xc0); outb(io | PC98SIO_baud_rate_port(if_type), (count >> 8) | 0x80); outb(io | PC98SIO_baud_rate_port(if_type), count & 0xff); break; case COM_IF_PIO9032B_1: case COM_IF_PIO9032B_2: outb(io | PC98SIO_baud_rate_port(if_type), count); break; case COM_IF_B98_01_1: case COM_IF_B98_01_2: outb(io | PC98SIO_baud_rate_port(if_type), count & 0x0f); #ifdef B98_01_OLD /* * Some old B98_01 board should be controlled * in different way, but this hasn't been tested yet. */ outb(io | PC98SIO_func_port(if_type), (count & 0x20000) ? 0xf0 : 0xf2); #endif break; } } static int pc98_check_if_type(device_t dev, struct siodev *iod) { int irr, io, if_type, tmp; static short irq_tab[2][8] = { { 3, 5, 6, 9, 10, 12, 13, -1}, { 3, 10, 12, 13, 5, 6, 9, -1} }; if_type = iod->if_type & 0x0f; iod->irq = 0; io = isa_get_port(dev) & 0xff00; if (IS_8251(iod->if_type)) { if (PC98SIO_func_port(if_type) != -1) { outb(io | PC98SIO_func_port(if_type), 0xf2); tmp = ttspeedtab(9600, if_8251_type[if_type].speedtab); if (tmp != -1 && PC98SIO_baud_rate_port(if_type) != -1) outb(io | PC98SIO_baud_rate_port(if_type), tmp); } iod->cmd = io | PC98SIO_cmd_port(if_type); iod->sts = io | PC98SIO_sts_port(if_type); iod->mod = io | PC98SIO_in_modem_port(if_type); iod->ctrl = io | PC98SIO_intr_ctrl_port(if_type); if (iod->if_type == COM_IF_INTERNAL) { iod->irq = 4; if (pc98_check_8251vfast()) { PC98SIO_baud_rate_port(if_type) = I8251F_div; if_8251_type[if_type].speedtab = pc98fast_speedtab; } } else { tmp = inb( iod->mod ) & if_8251_type[if_type].irr_mask; if ((isa_get_port(dev) & 0xff) == IO_COM2) iod->irq = irq_tab[0][tmp]; else iod->irq = irq_tab[1][tmp]; } } else { irr = if_16550a_type[if_type].irr_read; #ifdef COM_MULTIPORT if (!COM_ISMULTIPORT(device_get_flags(dev)) || device_get_unit(dev) == COM_MPMASTER(device_get_flags(dev))) #endif if (irr != -1) { tmp = inb(io | irr); if (isa_get_port(dev) & 0x01) /* XXX depend on RSB-384 */ iod->irq = irq_tab[1][tmp >> 3]; else iod->irq = irq_tab[0][tmp & 0x07]; } } if ( iod->irq == -1 ) return -1; return 0; } static void pc98_set_ioport(struct com_s *com) { int if_type = com->pc98_if_type & 0x0f; Port_t io = rman_get_start(com->ioportres) & 0xff00; pc98_check_sysclock(); com->data_port = io | PC98SIO_data_port(if_type); com->cmd_port = io | PC98SIO_cmd_port(if_type); com->sts_port = io | PC98SIO_sts_port(if_type); com->in_modem_port = io | PC98SIO_in_modem_port(if_type); com->intr_ctrl_port = io | PC98SIO_intr_ctrl_port(if_type); } static int pc98_check_8251vfast(void) { int i; outb(I8251F_div, 0x8c); DELAY(10); for (i = 0; i < 100; i++) { if ((inb(I8251F_div) & 0x80) != 0) { i = 0; break; } DELAY(1); } outb(I8251F_div, 0); DELAY(10); for (; i < 100; i++) { if ((inb(I8251F_div) & 0x80) == 0) return 1; DELAY(1); } return 0; } static int pc98_check_8251fifo(void) { u_char tmp1, tmp2; tmp1 = inb(I8251F_iir); DELAY(10); tmp2 = inb(I8251F_iir); if (((tmp1 ^ tmp2) & 0x40) != 0 && ((tmp1 | tmp2) & 0x20) == 0) return 1; return 0; } #endif /* PC98 defined */ diff --git a/sys/sys/tty.h b/sys/sys/tty.h index 0d16b6ac79ad..a9500cbef76c 100644 --- a/sys/sys/tty.h +++ b/sys/sys/tty.h @@ -1,320 +1,321 @@ /*- * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * Copyright (c) 2002 Networks Associates Technologies, Inc. * All rights reserved. * * Portions of this software were developed for the FreeBSD Project by * ThinkSec AS and NAI Labs, the Security Research Division of Network * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 * ("CBOSS"), as part of the DARPA CHATS research program. * * 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. * 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. * * @(#)tty.h 8.6 (Berkeley) 1/21/94 * $FreeBSD$ */ #ifndef _SYS_TTY_H_ #define _SYS_TTY_H_ #include #include #include /* * Clists are character lists, which is a variable length linked list * of cblocks, with a count of the number of characters in the list. */ struct clist { int c_cc; /* Number of characters in the clist. */ int c_cbcount; /* Number of cblocks. */ int c_cbmax; /* Max # cblocks allowed for this clist. */ int c_cbreserved; /* # cblocks reserved for this clist. */ char *c_cf; /* Pointer to the first cblock. */ char *c_cl; /* Pointer to the last cblock. */ }; /* * Per-tty structure. * * Should be split in two, into device and tty drivers. * Glue could be masks of what to echo and circular buffer * (low, high, timeout). */ struct tty { struct clist t_rawq; /* Device raw input queue. */ long t_rawcc; /* Raw input queue statistics. */ struct clist t_canq; /* Device canonical queue. */ long t_cancc; /* Canonical queue statistics. */ struct clist t_outq; /* Device output queue. */ long t_outcc; /* Output queue statistics. */ int t_line; /* Interface to device drivers. */ union { dev_t t_kdev; /* Device. */ udev_t t_udev; /* Userland (sysctl) instance. */ void *t_devp; /* Keep user/kernel size in sync. */ } ttyu; int t_state; /* Device and driver (TS*) state. */ int t_flags; /* Tty flags. */ int t_timeout; /* Timeout for ttywait() */ struct pgrp *t_pgrp; /* Foreground process group. */ struct session *t_session; /* Enclosing session. */ struct sigio *t_sigio; /* Information for async I/O. */ struct selinfo t_rsel; /* Tty read/oob select. */ struct selinfo t_wsel; /* Tty write select. */ struct termios t_termios; /* Termios state. */ struct winsize t_winsize; /* Window size. */ /* Start output. */ void (*t_oproc)(struct tty *); /* Stop output. */ void (*t_stop)(struct tty *, int); /* Set hardware state. */ int (*t_param)(struct tty *, struct termios *); void *t_sc; /* XXX: net/if_sl.c:sl_softc. */ int t_column; /* Tty output column. */ int t_rocount, t_rocol; /* Tty. */ int t_ififosize; /* Total size of upstream fifos. */ int t_ihiwat; /* High water mark for input. */ int t_ilowat; /* Low water mark for input. */ speed_t t_ispeedwat; /* t_ispeed override for watermarks. */ int t_ohiwat; /* High water mark for output. */ int t_olowat; /* Low water mark for output. */ speed_t t_ospeedwat; /* t_ospeed override for watermarks. */ int t_gen; /* Generation number. */ SLIST_ENTRY(tty) t_list; /* Global chain of ttys for pstat(8) */ }; #define t_cc t_termios.c_cc #define t_cflag t_termios.c_cflag #define t_dev ttyu.t_kdev #define t_iflag t_termios.c_iflag #define t_ispeed t_termios.c_ispeed #define t_lflag t_termios.c_lflag #define t_min t_termios.c_min #define t_oflag t_termios.c_oflag #define t_ospeed t_termios.c_ospeed #define t_time t_termios.c_time #define TTIPRI (PSOCK + 1) /* Sleep priority for tty reads. */ #define TTOPRI (PSOCK + 2) /* Sleep priority for tty writes. */ /* * Userland version of struct tty, for sysctl. */ struct xtty { size_t xt_size; /* Structure size. */ long xt_rawcc; /* Raw input queue statistics. */ long xt_cancc; /* Canonical queue statistics. */ long xt_outcc; /* Output queue statistics. */ int xt_line; /* Interface to device drivers. */ udev_t xt_dev; /* Userland (sysctl) instance. */ int xt_state; /* Device and driver (TS*) state. */ int xt_flags; /* Tty flags. */ int xt_timeout; /* Timeout for ttywait(). */ pid_t xt_pgid; /* Process group ID. */ pid_t xt_sid; /* Session ID. */ struct termios xt_termios; /* Termios state. */ struct winsize xt_winsize; /* Window size. */ int xt_column; /* Tty output column. */ int xt_rocount, xt_rocol; /* Tty. */ int xt_ififosize; /* Total size of upstream fifos. */ int xt_ihiwat; /* High water mark for input. */ int xt_ilowat; /* Low water mark for input. */ speed_t xt_ispeedwat; /* t_ispeed override for watermarks. */ int xt_ohiwat; /* High water mark for output. */ int xt_olowat; /* Low water mark for output. */ speed_t xt_ospeedwat; /* t_ospeed override for watermarks. */ }; /* * User data unfortunately has to be copied through buffers on the way to * and from clists. The buffers are on the stack so their sizes must be * fairly small. */ #define IBUFSIZ 384 /* Should be >= max value of MIN. */ #define OBUFSIZ 100 #ifndef TTYHOG #define TTYHOG 8192 #endif #ifdef _KERNEL #define TTMAXHIWAT roundup(2048, CBSIZE) #define TTMINHIWAT roundup(100, CBSIZE) #define TTMAXLOWAT 256 #define TTMINLOWAT 32 #endif /* These flags are kept in t_state. */ #define TS_SO_OLOWAT 0x00001 /* Wake up when output <= low water. */ #define TS_ASYNC 0x00002 /* Tty in async I/O mode. */ #define TS_BUSY 0x00004 /* Draining output. */ #define TS_CARR_ON 0x00008 /* Carrier is present. */ #define TS_FLUSH 0x00010 /* Outq has been flushed during DMA. */ #define TS_ISOPEN 0x00020 /* Open has completed. */ #define TS_TBLOCK 0x00040 /* Further input blocked. */ #define TS_TIMEOUT 0x00080 /* Wait for output char processing. */ #define TS_TTSTOP 0x00100 /* Output paused. */ #ifdef notyet #define TS_WOPEN 0x00200 /* Open in progress. */ #endif #define TS_XCLUDE 0x00400 /* Tty requires exclusivity. */ /* State for intra-line fancy editing work. */ #define TS_BKSL 0x00800 /* State for lowercase \ work. */ #define TS_CNTTB 0x01000 /* Counting tab width, ignore FLUSHO. */ #define TS_ERASE 0x02000 /* Within a \.../ for PRTRUB. */ #define TS_LNCH 0x04000 /* Next character is literal. */ #define TS_TYPEN 0x08000 /* Retyping suspended input (PENDIN). */ #define TS_LOCAL (TS_BKSL | TS_CNTTB | TS_ERASE | TS_LNCH | TS_TYPEN) /* Extras. */ #define TS_CAN_BYPASS_L_RINT 0x010000 /* Device in "raw" mode. */ #define TS_CONNECTED 0x020000 /* Connection open. */ #define TS_SNOOP 0x040000 /* Device is being snooped on. */ #define TS_SO_OCOMPLETE 0x080000 /* Wake up when output completes. */ #define TS_ZOMBIE 0x100000 /* Connection lost. */ /* Hardware flow-control-invoked bits. */ #define TS_CAR_OFLOW 0x200000 /* For MDMBUF (XXX handle in driver). */ #ifdef notyet #define TS_CTS_OFLOW 0x400000 /* For CCTS_OFLOW. */ #define TS_DSR_OFLOW 0x800000 /* For CDSR_OFLOW. */ #endif /* Character type information. */ #define ORDINARY 0 #define CONTROL 1 #define BACKSPACE 2 #define NEWLINE 3 #define TAB 4 #define VTAB 5 #define RETURN 6 struct speedtab { int sp_speed; /* Speed. */ int sp_code; /* Code. */ }; /* Modem control commands (driver). */ #define DMSET 0 #define DMBIS 1 #define DMBIC 2 #define DMGET 3 /* Flags on a character passed to ttyinput. */ #define TTY_CHARMASK 0x000000ff /* Character mask */ #define TTY_QUOTE 0x00000100 /* Character quoted */ #define TTY_ERRORMASK 0xff000000 /* Error mask */ #define TTY_FE 0x01000000 /* Framing error */ #define TTY_PE 0x02000000 /* Parity error */ #define TTY_OE 0x04000000 /* Overrun error */ #define TTY_BI 0x08000000 /* Break condition */ /* Is tp controlling terminal for p? */ #define isctty(p, tp) \ ((p)->p_session == (tp)->t_session && (p)->p_flag & P_CONTROLT) /* Is p in background of tp? */ #define isbackground(p, tp) \ (isctty((p), (tp)) && (p)->p_pgrp != (tp)->t_pgrp) /* Unique sleep addresses. */ #define TSA_CARR_ON(tp) ((void *)&(tp)->t_rawq) #define TSA_HUP_OR_INPUT(tp) ((void *)&(tp)->t_rawq.c_cf) #define TSA_OCOMPLETE(tp) ((void *)&(tp)->t_outq.c_cl) #define TSA_OLOWAT(tp) ((void *)&(tp)->t_outq) #define TSA_PTC_READ(tp) ((void *)&(tp)->t_outq.c_cf) #define TSA_PTC_WRITE(tp) ((void *)&(tp)->t_rawq.c_cl) #define TSA_PTS_READ(tp) ((void *)&(tp)->t_canq) #ifdef _KERNEL #ifdef MALLOC_DECLARE MALLOC_DECLARE(M_TTYS); #endif extern struct msgbuf consmsgbuf; /* Message buffer for constty. */ extern struct tty *constty; /* Temporary virtual console. */ extern long tk_cancc; extern long tk_nin; extern long tk_nout; extern long tk_rawcc; int b_to_q(char *cp, int cc, struct clist *q); void catq(struct clist *from, struct clist *to); void clist_alloc_cblocks(struct clist *q, int ccmax, int ccres); void clist_free_cblocks(struct clist *q); void constty_set(struct tty *tp); void constty_clear(void); int getc(struct clist *q); void ndflush(struct clist *q, int cc); char *nextc(struct clist *q, char *cp, int *c); void nottystop(struct tty *tp, int rw); int putc(int c, struct clist *q); int q_to_b(struct clist *q, char *cp, int cc); void termioschars(struct termios *t); int tputchar(int c, struct tty *tp); int ttcompat(struct tty *tp, u_long com, caddr_t data, int flag); int ttioctl(struct tty *tp, u_long com, void *data, int flag); int ttread(struct tty *tp, struct uio *uio, int flag); void ttrstrt(void *tp); int ttsetcompat(struct tty *tp, u_long *com, caddr_t data, struct termios *term); void ttsetwater(struct tty *tp); int ttspeedtab(int speed, struct speedtab *table); int ttstart(struct tty *tp); void ttwakeup(struct tty *tp); int ttwrite(struct tty *tp, struct uio *uio, int flag); void ttwwakeup(struct tty *tp); void ttyblock(struct tty *tp); void ttychars(struct tty *tp); int ttycheckoutq(struct tty *tp, int wait); int ttyclose(struct tty *tp); void ttyflush(struct tty *tp, int rw); void ttyfree(struct tty *tp); void ttyinfo(struct tty *tp); int ttyinput(int c, struct tty *tp); int ttylclose(struct tty *tp, int flag); +int ttyldoptim(struct tty *tp); struct tty *ttymalloc(struct tty *tp); int ttymodem(struct tty *tp, int flag); int ttyopen(dev_t device, struct tty *tp); int ttysleep(struct tty *tp, void *chan, int pri, char *wmesg, int timo); int ttywait(struct tty *tp); int unputc(struct clist *q); /* * XXX: temporary */ #include #endif /* _KERNEL */ #endif /* !_SYS_TTY_H_ */