e0cc03a09b
kqueue provides a stateful and efficient event notification framework currently supported events include socket, file, directory, fifo, pipe, tty and device changes, and monitoring of processes and signals kqueue is supported by all writable filesystems in NetBSD tree (with exception of Coda) and all device drivers supporting poll(2) based on work done by Jonathan Lemon for FreeBSD initial NetBSD port done by Luke Mewburn and Jason Thorpe
2273 lines
51 KiB
C
2273 lines
51 KiB
C
/* $NetBSD: plcom.c,v 1.5 2002/10/23 09:11:01 jdolecek Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2001 ARM Ltd
|
|
* 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 of the company may not be used to endorse or promote
|
|
* products derived from this software without specific prior written
|
|
* permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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.
|
|
*
|
|
* Copyright (c) 1998, 1999 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Charles M. Hannum.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* 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.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* @(#)com.c 7.5 (Berkeley) 5/16/91
|
|
*/
|
|
|
|
/*
|
|
* COM driver for the Prime Cell PL010 UART, which is similar to the 16C550,
|
|
* but has a completely different programmer's model.
|
|
* Derived from the NS16550AF com driver.
|
|
*/
|
|
|
|
#include "opt_plcom.h"
|
|
#include "opt_ddb.h"
|
|
#include "opt_kgdb.h"
|
|
|
|
#include "rnd.h"
|
|
#if NRND > 0 && defined(RND_COM)
|
|
#include <sys/rnd.h>
|
|
#endif
|
|
|
|
/*
|
|
* Override cnmagic(9) macro before including <sys/systm.h>.
|
|
* We need to know if cn_check_magic triggered debugger, so set a flag.
|
|
* Callers of cn_check_magic must declare int cn_trapped = 0;
|
|
* XXX: this is *ugly*!
|
|
*/
|
|
#define cn_trap() \
|
|
do { \
|
|
console_debugger(); \
|
|
cn_trapped = 1; \
|
|
} while (/* CONSTCOND */ 0)
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/select.h>
|
|
#include <sys/tty.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/user.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/file.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/types.h>
|
|
#include <sys/device.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/timepps.h>
|
|
#include <sys/vnode.h>
|
|
|
|
#include <machine/intr.h>
|
|
#include <machine/bus.h>
|
|
|
|
#include <evbarm/dev/plcomreg.h>
|
|
#include <evbarm/dev/plcomvar.h>
|
|
|
|
#include <dev/cons.h>
|
|
|
|
static void plcom_enable_debugport (struct plcom_softc *);
|
|
|
|
void plcom_config (struct plcom_softc *);
|
|
void plcom_shutdown (struct plcom_softc *);
|
|
int plcomspeed (long, long);
|
|
static u_char cflag2lcr (tcflag_t);
|
|
int plcomparam (struct tty *, struct termios *);
|
|
void plcomstart (struct tty *);
|
|
int plcomhwiflow (struct tty *, int);
|
|
|
|
void plcom_loadchannelregs (struct plcom_softc *);
|
|
void plcom_hwiflow (struct plcom_softc *);
|
|
void plcom_break (struct plcom_softc *, int);
|
|
void plcom_modem (struct plcom_softc *, int);
|
|
void tiocm_to_plcom (struct plcom_softc *, u_long, int);
|
|
int plcom_to_tiocm (struct plcom_softc *);
|
|
void plcom_iflush (struct plcom_softc *);
|
|
|
|
int plcom_common_getc (dev_t, bus_space_tag_t, bus_space_handle_t);
|
|
void plcom_common_putc (dev_t, bus_space_tag_t, bus_space_handle_t, int);
|
|
|
|
int plcominit (bus_space_tag_t, bus_addr_t, int, int, tcflag_t,
|
|
bus_space_handle_t *);
|
|
|
|
dev_type_open(plcomopen);
|
|
dev_type_close(plcomclose);
|
|
dev_type_read(plcomread);
|
|
dev_type_write(plcomwrite);
|
|
dev_type_ioctl(plcomioctl);
|
|
dev_type_stop(plcomstop);
|
|
dev_type_tty(plcomtty);
|
|
dev_type_poll(plcompoll);
|
|
|
|
int plcomcngetc (dev_t);
|
|
void plcomcnputc (dev_t, int);
|
|
void plcomcnpollc (dev_t, int);
|
|
|
|
#define integrate static inline
|
|
#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS
|
|
void plcomsoft (void *);
|
|
#else
|
|
#ifndef __NO_SOFT_SERIAL_INTERRUPT
|
|
void plcomsoft (void);
|
|
#else
|
|
void plcomsoft (void *);
|
|
struct callout plcomsoft_callout = CALLOUT_INITIALIZER;
|
|
#endif
|
|
#endif
|
|
integrate void plcom_rxsoft (struct plcom_softc *, struct tty *);
|
|
integrate void plcom_txsoft (struct plcom_softc *, struct tty *);
|
|
integrate void plcom_stsoft (struct plcom_softc *, struct tty *);
|
|
integrate void plcom_schedrx (struct plcom_softc *);
|
|
void plcomdiag (void *);
|
|
|
|
extern struct cfdriver plcom_cd;
|
|
|
|
const struct cdevsw plcom_cdevsw = {
|
|
plcomopen, plcomclose, plcomread, plcomwrite, plcomioctl,
|
|
plcomstop, plcomtty, plcompoll, nommap, ttykqfilter, D_TTY
|
|
};
|
|
|
|
/*
|
|
* Make this an option variable one can patch.
|
|
* But be warned: this must be a power of 2!
|
|
*/
|
|
u_int plcom_rbuf_size = PLCOM_RING_SIZE;
|
|
|
|
/* Stop input when 3/4 of the ring is full; restart when only 1/4 is full. */
|
|
u_int plcom_rbuf_hiwat = (PLCOM_RING_SIZE * 1) / 4;
|
|
u_int plcom_rbuf_lowat = (PLCOM_RING_SIZE * 3) / 4;
|
|
|
|
static int plcomconsunit = -1;
|
|
static bus_space_tag_t plcomconstag;
|
|
static bus_space_handle_t plcomconsioh;
|
|
static int plcomconsattached;
|
|
static int plcomconsrate;
|
|
static tcflag_t plcomconscflag;
|
|
static struct cnm_state plcom_cnm_state;
|
|
|
|
static int ppscap =
|
|
PPS_TSFMT_TSPEC |
|
|
PPS_CAPTUREASSERT |
|
|
PPS_CAPTURECLEAR |
|
|
#ifdef PPS_SYNC
|
|
PPS_HARDPPSONASSERT | PPS_HARDPPSONCLEAR |
|
|
#endif /* PPS_SYNC */
|
|
PPS_OFFSETASSERT | PPS_OFFSETCLEAR;
|
|
|
|
#ifndef __HAVE_GENERIC_SOFT_INTERRUPTS
|
|
#ifdef __NO_SOFT_SERIAL_INTERRUPT
|
|
volatile int plcom_softintr_scheduled;
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef KGDB
|
|
#include <sys/kgdb.h>
|
|
|
|
static int plcom_kgdb_unit;
|
|
static bus_space_tag_t plcom_kgdb_iot;
|
|
static bus_space_handle_t plcom_kgdb_ioh;
|
|
static int plcom_kgdb_attached;
|
|
|
|
int plcom_kgdb_getc (void *);
|
|
void plcom_kgdb_putc (void *, int);
|
|
#endif /* KGDB */
|
|
|
|
#define PLCOMUNIT_MASK 0x7ffff
|
|
#define PLCOMDIALOUT_MASK 0x80000
|
|
|
|
#define PLCOMUNIT(x) (minor(x) & PLCOMUNIT_MASK)
|
|
#define PLCOMDIALOUT(x) (minor(x) & PLCOMDIALOUT_MASK)
|
|
|
|
#define PLCOM_ISALIVE(sc) ((sc)->enabled != 0 && \
|
|
ISSET((sc)->sc_dev.dv_flags, DVF_ACTIVE))
|
|
|
|
#define BR BUS_SPACE_BARRIER_READ
|
|
#define BW BUS_SPACE_BARRIER_WRITE
|
|
#define PLCOM_BARRIER(t, h, f) bus_space_barrier((t), (h), 0, PLCOM_UART_SIZE, (f))
|
|
|
|
#if (defined(MULTIPROCESSOR) || defined(LOCKDEBUG)) && defined(PLCOM_MPLOCK)
|
|
|
|
#define PLCOM_LOCK(sc) simple_lock(&(sc)->sc_lock)
|
|
#define PLCOM_UNLOCK(sc) simple_unlock(&(sc)->sc_lock)
|
|
|
|
#else
|
|
|
|
#define PLCOM_LOCK(sc)
|
|
#define PLCOM_UNLOCK(sc)
|
|
|
|
#endif
|
|
|
|
int
|
|
plcomspeed(long speed, long frequency)
|
|
{
|
|
#define divrnd(n, q) (((n)*2/(q)+1)/2) /* divide and round off */
|
|
|
|
int x, err;
|
|
|
|
#if 0
|
|
if (speed == 0)
|
|
return 0;
|
|
#endif
|
|
if (speed <= 0)
|
|
return -1;
|
|
x = divrnd(frequency / 16, speed);
|
|
if (x <= 0)
|
|
return -1;
|
|
err = divrnd(((quad_t)frequency) * 1000 / 16, speed * x) - 1000;
|
|
if (err < 0)
|
|
err = -err;
|
|
if (err > PLCOM_TOLERANCE)
|
|
return -1;
|
|
return x;
|
|
|
|
#undef divrnd
|
|
}
|
|
|
|
#ifdef PLCOM_DEBUG
|
|
int plcom_debug = 0;
|
|
|
|
void plcomstatus (struct plcom_softc *, char *);
|
|
void
|
|
plcomstatus(struct plcom_softc *sc, char *str)
|
|
{
|
|
struct tty *tp = sc->sc_tty;
|
|
|
|
printf("%s: %s %sclocal %sdcd %sts_carr_on %sdtr %stx_stopped\n",
|
|
sc->sc_dev.dv_xname, str,
|
|
ISSET(tp->t_cflag, CLOCAL) ? "+" : "-",
|
|
ISSET(sc->sc_msr, MSR_DCD) ? "+" : "-",
|
|
ISSET(tp->t_state, TS_CARR_ON) ? "+" : "-",
|
|
ISSET(sc->sc_mcr, MCR_DTR) ? "+" : "-",
|
|
sc->sc_tx_stopped ? "+" : "-");
|
|
|
|
printf("%s: %s %scrtscts %scts %sts_ttstop %srts %xrx_flags\n",
|
|
sc->sc_dev.dv_xname, str,
|
|
ISSET(tp->t_cflag, CRTSCTS) ? "+" : "-",
|
|
ISSET(sc->sc_msr, MSR_CTS) ? "+" : "-",
|
|
ISSET(tp->t_state, TS_TTSTOP) ? "+" : "-",
|
|
ISSET(sc->sc_mcr, MCR_RTS) ? "+" : "-",
|
|
sc->sc_rx_flags);
|
|
}
|
|
#endif
|
|
|
|
int
|
|
plcomprobe1(bus_space_tag_t iot, bus_space_handle_t ioh)
|
|
{
|
|
int data;
|
|
|
|
/* Disable the UART. */
|
|
bus_space_write_1(iot, ioh, plcom_cr, 0);
|
|
/* Make sure the FIFO is off. */
|
|
bus_space_write_1(iot, ioh, plcom_lcr, LCR_8BITS);
|
|
/* Disable interrupts. */
|
|
bus_space_write_1(iot, ioh, plcom_iir, 0);
|
|
|
|
/* Make sure we swallow anything in the receiving register. */
|
|
data = bus_space_read_1(iot, ioh, plcom_dr);
|
|
|
|
if (bus_space_read_1(iot, ioh, plcom_lcr) != LCR_8BITS)
|
|
return 0;
|
|
|
|
data = bus_space_read_1(iot, ioh, plcom_fr) & (FR_RXFF | FR_RXFE);
|
|
|
|
if (data != FR_RXFE)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
plcom_enable_debugport(struct plcom_softc *sc)
|
|
{
|
|
int s;
|
|
|
|
/* Turn on line break interrupt, set carrier. */
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
sc->sc_cr = CR_RIE | CR_RTIE | CR_UARTEN;
|
|
bus_space_write_1(sc->sc_iot, sc->sc_ioh, plcom_cr, sc->sc_cr);
|
|
SET(sc->sc_mcr, MCR_DTR | MCR_RTS);
|
|
sc->sc_set_mcr(sc->sc_set_mcr_arg, sc->sc_dev.dv_unit, sc->sc_mcr);
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
plcom_attach_subr(struct plcom_softc *sc)
|
|
{
|
|
int unit = sc->sc_iounit;
|
|
bus_space_tag_t iot = sc->sc_iot;
|
|
bus_space_handle_t ioh = sc->sc_ioh;
|
|
struct tty *tp;
|
|
|
|
callout_init(&sc->sc_diag_callout);
|
|
#if (defined(MULTIPROCESSOR) || defined(LOCKDEBUG)) && defined(PLCOM_MPLOCK)
|
|
simple_lock_init(&sc->sc_lock);
|
|
#endif
|
|
|
|
/* Disable interrupts before configuring the device. */
|
|
sc->sc_cr = 0;
|
|
|
|
if (plcomconstag && unit == plcomconsunit) {
|
|
plcomconsattached = 1;
|
|
|
|
plcomconstag = iot;
|
|
plcomconsioh = ioh;
|
|
|
|
/* Make sure the console is always "hardwired". */
|
|
delay(1000); /* wait for output to finish */
|
|
SET(sc->sc_hwflags, PLCOM_HW_CONSOLE);
|
|
SET(sc->sc_swflags, TIOCFLAG_SOFTCAR);
|
|
/* Must re-enable the console immediately, or we will
|
|
hang when trying to print. */
|
|
sc->sc_cr = CR_UARTEN;
|
|
}
|
|
|
|
bus_space_write_1(iot, ioh, plcom_cr, sc->sc_cr);
|
|
|
|
/* The PL010 has a 16-byte fifo, but the tx interrupt triggers when
|
|
there is space for 8 more bytes. */
|
|
sc->sc_fifolen = 8;
|
|
printf("\n");
|
|
|
|
if (ISSET(sc->sc_hwflags, PLCOM_HW_TXFIFO_DISABLE)) {
|
|
sc->sc_fifolen = 1;
|
|
printf("%s: txfifo disabled\n", sc->sc_dev.dv_xname);
|
|
}
|
|
|
|
if (sc->sc_fifolen > 1)
|
|
SET(sc->sc_hwflags, PLCOM_HW_FIFO);
|
|
|
|
tp = ttymalloc();
|
|
tp->t_oproc = plcomstart;
|
|
tp->t_param = plcomparam;
|
|
tp->t_hwiflow = plcomhwiflow;
|
|
|
|
sc->sc_tty = tp;
|
|
sc->sc_rbuf = malloc(plcom_rbuf_size << 1, M_DEVBUF, M_NOWAIT);
|
|
sc->sc_rbput = sc->sc_rbget = sc->sc_rbuf;
|
|
sc->sc_rbavail = plcom_rbuf_size;
|
|
if (sc->sc_rbuf == NULL) {
|
|
printf("%s: unable to allocate ring buffer\n",
|
|
sc->sc_dev.dv_xname);
|
|
return;
|
|
}
|
|
sc->sc_ebuf = sc->sc_rbuf + (plcom_rbuf_size << 1);
|
|
|
|
tty_attach(tp);
|
|
|
|
if (ISSET(sc->sc_hwflags, PLCOM_HW_CONSOLE)) {
|
|
int maj;
|
|
|
|
/* locate the major number */
|
|
maj = cdevsw_lookup_major(&plcom_cdevsw);
|
|
|
|
cn_tab->cn_dev = makedev(maj, sc->sc_dev.dv_unit);
|
|
|
|
printf("%s: console\n", sc->sc_dev.dv_xname);
|
|
}
|
|
|
|
#ifdef KGDB
|
|
/*
|
|
* Allow kgdb to "take over" this port. If this is
|
|
* the kgdb device, it has exclusive use.
|
|
*/
|
|
if (iot == plcom_kgdb_iot && unit == plcom_kgdb_unit) {
|
|
plcom_kgdb_attached = 1;
|
|
|
|
SET(sc->sc_hwflags, PLCOM_HW_KGDB);
|
|
printf("%s: kgdb\n", sc->sc_dev.dv_xname);
|
|
}
|
|
#endif
|
|
|
|
#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS
|
|
sc->sc_si = softintr_establish(IPL_SOFTSERIAL, plcomsoft, sc);
|
|
#endif
|
|
|
|
#if NRND > 0 && defined(RND_COM)
|
|
rnd_attach_source(&sc->rnd_source, sc->sc_dev.dv_xname,
|
|
RND_TYPE_TTY, 0);
|
|
#endif
|
|
|
|
/* if there are no enable/disable functions, assume the device
|
|
is always enabled */
|
|
if (!sc->enable)
|
|
sc->enabled = 1;
|
|
|
|
plcom_config(sc);
|
|
|
|
SET(sc->sc_hwflags, PLCOM_HW_DEV_OK);
|
|
}
|
|
|
|
void
|
|
plcom_config(struct plcom_softc *sc)
|
|
{
|
|
bus_space_tag_t iot = sc->sc_iot;
|
|
bus_space_handle_t ioh = sc->sc_ioh;
|
|
|
|
/* Disable interrupts before configuring the device. */
|
|
sc->sc_cr = 0;
|
|
bus_space_write_1(iot, ioh, plcom_cr, sc->sc_cr);
|
|
|
|
if (ISSET(sc->sc_hwflags, PLCOM_HW_CONSOLE|PLCOM_HW_KGDB))
|
|
plcom_enable_debugport(sc);
|
|
}
|
|
|
|
int
|
|
plcom_detach(self, flags)
|
|
struct device *self;
|
|
int flags;
|
|
{
|
|
struct plcom_softc *sc = (struct plcom_softc *)self;
|
|
int maj, mn;
|
|
|
|
/* locate the major number */
|
|
maj = cdevsw_lookup_major(&plcom_cdevsw);
|
|
|
|
/* Nuke the vnodes for any open instances. */
|
|
mn = self->dv_unit;
|
|
vdevgone(maj, mn, mn, VCHR);
|
|
|
|
mn |= PLCOMDIALOUT_MASK;
|
|
vdevgone(maj, mn, mn, VCHR);
|
|
|
|
/* Free the receive buffer. */
|
|
free(sc->sc_rbuf, M_DEVBUF);
|
|
|
|
/* Detach and free the tty. */
|
|
tty_detach(sc->sc_tty);
|
|
ttyfree(sc->sc_tty);
|
|
|
|
#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS
|
|
/* Unhook the soft interrupt handler. */
|
|
softintr_disestablish(sc->sc_si);
|
|
#endif
|
|
|
|
#if NRND > 0 && defined(RND_COM)
|
|
/* Unhook the entropy source. */
|
|
rnd_detach_source(&sc->rnd_source);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
plcom_activate(struct device *self, enum devact act)
|
|
{
|
|
struct plcom_softc *sc = (struct plcom_softc *)self;
|
|
int s, rv = 0;
|
|
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
switch (act) {
|
|
case DVACT_ACTIVATE:
|
|
rv = EOPNOTSUPP;
|
|
break;
|
|
|
|
case DVACT_DEACTIVATE:
|
|
if (sc->sc_hwflags & (PLCOM_HW_CONSOLE|PLCOM_HW_KGDB)) {
|
|
rv = EBUSY;
|
|
break;
|
|
}
|
|
|
|
if (sc->disable != NULL && sc->enabled != 0) {
|
|
(*sc->disable)(sc);
|
|
sc->enabled = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
plcom_shutdown(struct plcom_softc *sc)
|
|
{
|
|
struct tty *tp = sc->sc_tty;
|
|
int s;
|
|
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
|
|
/* If we were asserting flow control, then deassert it. */
|
|
SET(sc->sc_rx_flags, RX_IBUF_BLOCKED);
|
|
plcom_hwiflow(sc);
|
|
|
|
/* Clear any break condition set with TIOCSBRK. */
|
|
plcom_break(sc, 0);
|
|
|
|
/* Turn off PPS capture on last close. */
|
|
sc->sc_ppsmask = 0;
|
|
sc->ppsparam.mode = 0;
|
|
|
|
/*
|
|
* Hang up if necessary. Wait a bit, so the other side has time to
|
|
* notice even if we immediately open the port again.
|
|
* Avoid tsleeping above splhigh().
|
|
*/
|
|
if (ISSET(tp->t_cflag, HUPCL)) {
|
|
plcom_modem(sc, 0);
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
/* XXX tsleep will only timeout */
|
|
(void) tsleep(sc, TTIPRI, ttclos, hz);
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
}
|
|
|
|
/* Turn off interrupts. */
|
|
if (ISSET(sc->sc_hwflags, PLCOM_HW_CONSOLE))
|
|
/* interrupt on break */
|
|
sc->sc_cr = CR_RIE | CR_RTIE | CR_UARTEN;
|
|
else
|
|
sc->sc_cr = 0;
|
|
bus_space_write_1(sc->sc_iot, sc->sc_ioh, plcom_cr, sc->sc_cr);
|
|
|
|
if (sc->disable) {
|
|
#ifdef DIAGNOSTIC
|
|
if (!sc->enabled)
|
|
panic("plcom_shutdown: not enabled?");
|
|
#endif
|
|
(*sc->disable)(sc);
|
|
sc->enabled = 0;
|
|
}
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
}
|
|
|
|
int
|
|
plcomopen(dev_t dev, int flag, int mode, struct proc *p)
|
|
{
|
|
struct plcom_softc *sc;
|
|
struct tty *tp;
|
|
int s, s2;
|
|
int error;
|
|
|
|
sc = device_lookup(&plcom_cd, PLCOMUNIT(dev));
|
|
if (sc == NULL || !ISSET(sc->sc_hwflags, PLCOM_HW_DEV_OK) ||
|
|
sc->sc_rbuf == NULL)
|
|
return ENXIO;
|
|
|
|
if (ISSET(sc->sc_dev.dv_flags, DVF_ACTIVE) == 0)
|
|
return ENXIO;
|
|
|
|
#ifdef KGDB
|
|
/*
|
|
* If this is the kgdb port, no other use is permitted.
|
|
*/
|
|
if (ISSET(sc->sc_hwflags, PLCOM_HW_KGDB))
|
|
return EBUSY;
|
|
#endif
|
|
|
|
tp = sc->sc_tty;
|
|
|
|
if (ISSET(tp->t_state, TS_ISOPEN) &&
|
|
ISSET(tp->t_state, TS_XCLUDE) &&
|
|
p->p_ucred->cr_uid != 0)
|
|
return EBUSY;
|
|
|
|
s = spltty();
|
|
|
|
/*
|
|
* Do the following iff this is a first open.
|
|
*/
|
|
if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
|
|
struct termios t;
|
|
|
|
tp->t_dev = dev;
|
|
|
|
s2 = splserial();
|
|
PLCOM_LOCK(sc);
|
|
|
|
if (sc->enable) {
|
|
if ((*sc->enable)(sc)) {
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s2);
|
|
splx(s);
|
|
printf("%s: device enable failed\n",
|
|
sc->sc_dev.dv_xname);
|
|
return EIO;
|
|
}
|
|
sc->enabled = 1;
|
|
plcom_config(sc);
|
|
}
|
|
|
|
/* Turn on interrupts. */
|
|
/* IER_ERXRDY | IER_ERLS | IER_EMSC; */
|
|
sc->sc_cr = CR_RIE | CR_RTIE | CR_MSIE | CR_UARTEN;
|
|
bus_space_write_1(sc->sc_iot, sc->sc_ioh, plcom_cr, sc->sc_cr);
|
|
|
|
/* Fetch the current modem control status, needed later. */
|
|
sc->sc_msr = bus_space_read_1(sc->sc_iot, sc->sc_ioh, plcom_fr);
|
|
|
|
/* Clear PPS capture state on first open. */
|
|
sc->sc_ppsmask = 0;
|
|
sc->ppsparam.mode = 0;
|
|
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s2);
|
|
|
|
/*
|
|
* Initialize the termios status to the defaults. Add in the
|
|
* sticky bits from TIOCSFLAGS.
|
|
*/
|
|
t.c_ispeed = 0;
|
|
if (ISSET(sc->sc_hwflags, PLCOM_HW_CONSOLE)) {
|
|
t.c_ospeed = plcomconsrate;
|
|
t.c_cflag = plcomconscflag;
|
|
} else {
|
|
t.c_ospeed = TTYDEF_SPEED;
|
|
t.c_cflag = TTYDEF_CFLAG;
|
|
}
|
|
if (ISSET(sc->sc_swflags, TIOCFLAG_CLOCAL))
|
|
SET(t.c_cflag, CLOCAL);
|
|
if (ISSET(sc->sc_swflags, TIOCFLAG_CRTSCTS))
|
|
SET(t.c_cflag, CRTSCTS);
|
|
if (ISSET(sc->sc_swflags, TIOCFLAG_MDMBUF))
|
|
SET(t.c_cflag, MDMBUF);
|
|
/* Make sure plcomparam() will do something. */
|
|
tp->t_ospeed = 0;
|
|
(void) plcomparam(tp, &t);
|
|
tp->t_iflag = TTYDEF_IFLAG;
|
|
tp->t_oflag = TTYDEF_OFLAG;
|
|
tp->t_lflag = TTYDEF_LFLAG;
|
|
ttychars(tp);
|
|
ttsetwater(tp);
|
|
|
|
s2 = splserial();
|
|
PLCOM_LOCK(sc);
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
plcom_modem(sc, 1);
|
|
|
|
/* Clear the input ring, and unblock. */
|
|
sc->sc_rbput = sc->sc_rbget = sc->sc_rbuf;
|
|
sc->sc_rbavail = plcom_rbuf_size;
|
|
plcom_iflush(sc);
|
|
CLR(sc->sc_rx_flags, RX_ANY_BLOCK);
|
|
plcom_hwiflow(sc);
|
|
|
|
#ifdef PLCOM_DEBUG
|
|
if (plcom_debug)
|
|
plcomstatus(sc, "plcomopen ");
|
|
#endif
|
|
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s2);
|
|
}
|
|
|
|
splx(s);
|
|
|
|
error = ttyopen(tp, PLCOMDIALOUT(dev), ISSET(flag, O_NONBLOCK));
|
|
if (error)
|
|
goto bad;
|
|
|
|
error = (*tp->t_linesw->l_open)(dev, tp);
|
|
if (error)
|
|
goto bad;
|
|
|
|
return 0;
|
|
|
|
bad:
|
|
if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
|
|
/*
|
|
* We failed to open the device, and nobody else had it opened.
|
|
* Clean up the state as appropriate.
|
|
*/
|
|
plcom_shutdown(sc);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
int
|
|
plcomclose(dev_t dev, int flag, int mode, struct proc *p)
|
|
{
|
|
struct plcom_softc *sc = device_lookup(&plcom_cd, PLCOMUNIT(dev));
|
|
struct tty *tp = sc->sc_tty;
|
|
|
|
/* XXX This is for cons.c. */
|
|
if (!ISSET(tp->t_state, TS_ISOPEN))
|
|
return 0;
|
|
|
|
(*tp->t_linesw->l_close)(tp, flag);
|
|
ttyclose(tp);
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
return 0;
|
|
|
|
if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
|
|
/*
|
|
* 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.
|
|
*/
|
|
plcom_shutdown(sc);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
plcomread(dev_t dev, struct uio *uio, int flag)
|
|
{
|
|
struct plcom_softc *sc = device_lookup(&plcom_cd, PLCOMUNIT(dev));
|
|
struct tty *tp = sc->sc_tty;
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
return EIO;
|
|
|
|
return (*tp->t_linesw->l_read)(tp, uio, flag);
|
|
}
|
|
|
|
int
|
|
plcomwrite(dev_t dev, struct uio *uio, int flag)
|
|
{
|
|
struct plcom_softc *sc = device_lookup(&plcom_cd, PLCOMUNIT(dev));
|
|
struct tty *tp = sc->sc_tty;
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
return EIO;
|
|
|
|
return (*tp->t_linesw->l_write)(tp, uio, flag);
|
|
}
|
|
|
|
int
|
|
plcompoll(dev_t dev, int events, struct proc *p)
|
|
{
|
|
struct plcom_softc *sc = device_lookup(&plcom_cd, PLCOMUNIT(dev));
|
|
struct tty *tp = sc->sc_tty;
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
return EIO;
|
|
|
|
return (*tp->t_linesw->l_poll)(tp, events, p);
|
|
}
|
|
|
|
struct tty *
|
|
plcomtty(dev_t dev)
|
|
{
|
|
struct plcom_softc *sc = device_lookup(&plcom_cd, PLCOMUNIT(dev));
|
|
struct tty *tp = sc->sc_tty;
|
|
|
|
return tp;
|
|
}
|
|
|
|
int
|
|
plcomioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
|
|
{
|
|
struct plcom_softc *sc = device_lookup(&plcom_cd, PLCOMUNIT(dev));
|
|
struct tty *tp = sc->sc_tty;
|
|
int error;
|
|
int s;
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
return EIO;
|
|
|
|
error = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, p);
|
|
if (error != EPASSTHROUGH)
|
|
return error;
|
|
|
|
error = ttioctl(tp, cmd, data, flag, p);
|
|
if (error != EPASSTHROUGH)
|
|
return error;
|
|
|
|
error = 0;
|
|
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
|
|
switch (cmd) {
|
|
case TIOCSBRK:
|
|
plcom_break(sc, 1);
|
|
break;
|
|
|
|
case TIOCCBRK:
|
|
plcom_break(sc, 0);
|
|
break;
|
|
|
|
case TIOCSDTR:
|
|
plcom_modem(sc, 1);
|
|
break;
|
|
|
|
case TIOCCDTR:
|
|
plcom_modem(sc, 0);
|
|
break;
|
|
|
|
case TIOCGFLAGS:
|
|
*(int *)data = sc->sc_swflags;
|
|
break;
|
|
|
|
case TIOCSFLAGS:
|
|
error = suser(p->p_ucred, &p->p_acflag);
|
|
if (error)
|
|
break;
|
|
sc->sc_swflags = *(int *)data;
|
|
break;
|
|
|
|
case TIOCMSET:
|
|
case TIOCMBIS:
|
|
case TIOCMBIC:
|
|
tiocm_to_plcom(sc, cmd, *(int *)data);
|
|
break;
|
|
|
|
case TIOCMGET:
|
|
*(int *)data = plcom_to_tiocm(sc);
|
|
break;
|
|
|
|
case PPS_IOC_CREATE:
|
|
break;
|
|
|
|
case PPS_IOC_DESTROY:
|
|
break;
|
|
|
|
case PPS_IOC_GETPARAMS: {
|
|
pps_params_t *pp;
|
|
pp = (pps_params_t *)data;
|
|
*pp = sc->ppsparam;
|
|
break;
|
|
}
|
|
|
|
case PPS_IOC_SETPARAMS: {
|
|
pps_params_t *pp;
|
|
int mode;
|
|
pp = (pps_params_t *)data;
|
|
if (pp->mode & ~ppscap) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
sc->ppsparam = *pp;
|
|
/*
|
|
* Compute msr masks from user-specified timestamp state.
|
|
*/
|
|
mode = sc->ppsparam.mode;
|
|
#ifdef PPS_SYNC
|
|
if (mode & PPS_HARDPPSONASSERT) {
|
|
mode |= PPS_CAPTUREASSERT;
|
|
/* XXX revoke any previous HARDPPS source */
|
|
}
|
|
if (mode & PPS_HARDPPSONCLEAR) {
|
|
mode |= PPS_CAPTURECLEAR;
|
|
/* XXX revoke any previous HARDPPS source */
|
|
}
|
|
#endif /* PPS_SYNC */
|
|
switch (mode & PPS_CAPTUREBOTH) {
|
|
case 0:
|
|
sc->sc_ppsmask = 0;
|
|
break;
|
|
|
|
case PPS_CAPTUREASSERT:
|
|
sc->sc_ppsmask = MSR_DCD;
|
|
sc->sc_ppsassert = MSR_DCD;
|
|
sc->sc_ppsclear = -1;
|
|
break;
|
|
|
|
case PPS_CAPTURECLEAR:
|
|
sc->sc_ppsmask = MSR_DCD;
|
|
sc->sc_ppsassert = -1;
|
|
sc->sc_ppsclear = 0;
|
|
break;
|
|
|
|
case PPS_CAPTUREBOTH:
|
|
sc->sc_ppsmask = MSR_DCD;
|
|
sc->sc_ppsassert = MSR_DCD;
|
|
sc->sc_ppsclear = 0;
|
|
break;
|
|
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PPS_IOC_GETCAP:
|
|
*(int*)data = ppscap;
|
|
break;
|
|
|
|
case PPS_IOC_FETCH: {
|
|
pps_info_t *pi;
|
|
pi = (pps_info_t *)data;
|
|
*pi = sc->ppsinfo;
|
|
break;
|
|
}
|
|
|
|
case TIOCDCDTIMESTAMP: /* XXX old, overloaded API used by xntpd v3 */
|
|
/*
|
|
* Some GPS clocks models use the falling rather than
|
|
* rising edge as the on-the-second signal.
|
|
* The old API has no way to specify PPS polarity.
|
|
*/
|
|
sc->sc_ppsmask = MSR_DCD;
|
|
#ifndef PPS_TRAILING_EDGE
|
|
sc->sc_ppsassert = MSR_DCD;
|
|
sc->sc_ppsclear = -1;
|
|
TIMESPEC_TO_TIMEVAL((struct timeval *)data,
|
|
&sc->ppsinfo.assert_timestamp);
|
|
#else
|
|
sc->sc_ppsassert = -1
|
|
sc->sc_ppsclear = 0;
|
|
TIMESPEC_TO_TIMEVAL((struct timeval *)data,
|
|
&sc->ppsinfo.clear_timestamp);
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
error = EPASSTHROUGH;
|
|
break;
|
|
}
|
|
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
|
|
#ifdef PLCOM_DEBUG
|
|
if (plcom_debug)
|
|
plcomstatus(sc, "plcomioctl ");
|
|
#endif
|
|
|
|
return error;
|
|
}
|
|
|
|
integrate void
|
|
plcom_schedrx(struct plcom_softc *sc)
|
|
{
|
|
|
|
sc->sc_rx_ready = 1;
|
|
|
|
/* Wake up the poller. */
|
|
#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS
|
|
softintr_schedule(sc->sc_si);
|
|
#else
|
|
#ifndef __NO_SOFT_SERIAL_INTERRUPT
|
|
setsoftserial();
|
|
#else
|
|
if (!plcom_softintr_scheduled) {
|
|
plcom_softintr_scheduled = 1;
|
|
callout_reset(&plcomsoft_callout, 1, plcomsoft, NULL);
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
void
|
|
plcom_break(struct plcom_softc *sc, int onoff)
|
|
{
|
|
|
|
if (onoff)
|
|
SET(sc->sc_lcr, LCR_BRK);
|
|
else
|
|
CLR(sc->sc_lcr, LCR_BRK);
|
|
|
|
if (!sc->sc_heldchange) {
|
|
if (sc->sc_tx_busy) {
|
|
sc->sc_heldtbc = sc->sc_tbc;
|
|
sc->sc_tbc = 0;
|
|
sc->sc_heldchange = 1;
|
|
} else
|
|
plcom_loadchannelregs(sc);
|
|
}
|
|
}
|
|
|
|
void
|
|
plcom_modem(struct plcom_softc *sc, int onoff)
|
|
{
|
|
|
|
if (sc->sc_mcr_dtr == 0)
|
|
return;
|
|
|
|
if (onoff)
|
|
SET(sc->sc_mcr, sc->sc_mcr_dtr);
|
|
else
|
|
CLR(sc->sc_mcr, sc->sc_mcr_dtr);
|
|
|
|
if (!sc->sc_heldchange) {
|
|
if (sc->sc_tx_busy) {
|
|
sc->sc_heldtbc = sc->sc_tbc;
|
|
sc->sc_tbc = 0;
|
|
sc->sc_heldchange = 1;
|
|
} else
|
|
plcom_loadchannelregs(sc);
|
|
}
|
|
}
|
|
|
|
void
|
|
tiocm_to_plcom(struct plcom_softc *sc, u_long how, int ttybits)
|
|
{
|
|
u_char plcombits;
|
|
|
|
plcombits = 0;
|
|
if (ISSET(ttybits, TIOCM_DTR))
|
|
SET(plcombits, MCR_DTR);
|
|
if (ISSET(ttybits, TIOCM_RTS))
|
|
SET(plcombits, MCR_RTS);
|
|
|
|
switch (how) {
|
|
case TIOCMBIC:
|
|
CLR(sc->sc_mcr, plcombits);
|
|
break;
|
|
|
|
case TIOCMBIS:
|
|
SET(sc->sc_mcr, plcombits);
|
|
break;
|
|
|
|
case TIOCMSET:
|
|
CLR(sc->sc_mcr, MCR_DTR | MCR_RTS);
|
|
SET(sc->sc_mcr, plcombits);
|
|
break;
|
|
}
|
|
|
|
if (!sc->sc_heldchange) {
|
|
if (sc->sc_tx_busy) {
|
|
sc->sc_heldtbc = sc->sc_tbc;
|
|
sc->sc_tbc = 0;
|
|
sc->sc_heldchange = 1;
|
|
} else
|
|
plcom_loadchannelregs(sc);
|
|
}
|
|
}
|
|
|
|
int
|
|
plcom_to_tiocm(struct plcom_softc *sc)
|
|
{
|
|
u_char plcombits;
|
|
int ttybits = 0;
|
|
|
|
plcombits = sc->sc_mcr;
|
|
if (ISSET(plcombits, MCR_DTR))
|
|
SET(ttybits, TIOCM_DTR);
|
|
if (ISSET(plcombits, MCR_RTS))
|
|
SET(ttybits, TIOCM_RTS);
|
|
|
|
plcombits = sc->sc_msr;
|
|
if (ISSET(plcombits, MSR_DCD))
|
|
SET(ttybits, TIOCM_CD);
|
|
if (ISSET(plcombits, MSR_CTS))
|
|
SET(ttybits, TIOCM_CTS);
|
|
if (ISSET(plcombits, MSR_DSR))
|
|
SET(ttybits, TIOCM_DSR);
|
|
|
|
if (sc->sc_cr != 0)
|
|
SET(ttybits, TIOCM_LE);
|
|
|
|
return ttybits;
|
|
}
|
|
|
|
static u_char
|
|
cflag2lcr(tcflag_t cflag)
|
|
{
|
|
u_char lcr = 0;
|
|
|
|
switch (ISSET(cflag, CSIZE)) {
|
|
case CS5:
|
|
SET(lcr, LCR_5BITS);
|
|
break;
|
|
case CS6:
|
|
SET(lcr, LCR_6BITS);
|
|
break;
|
|
case CS7:
|
|
SET(lcr, LCR_7BITS);
|
|
break;
|
|
case CS8:
|
|
SET(lcr, LCR_8BITS);
|
|
break;
|
|
}
|
|
if (ISSET(cflag, PARENB)) {
|
|
SET(lcr, LCR_PEN);
|
|
if (!ISSET(cflag, PARODD))
|
|
SET(lcr, LCR_EPS);
|
|
}
|
|
if (ISSET(cflag, CSTOPB))
|
|
SET(lcr, LCR_STP2);
|
|
|
|
return lcr;
|
|
}
|
|
|
|
int
|
|
plcomparam(struct tty *tp, struct termios *t)
|
|
{
|
|
struct plcom_softc *sc = device_lookup(&plcom_cd, PLCOMUNIT(tp->t_dev));
|
|
int ospeed;
|
|
u_char lcr;
|
|
int s;
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
return EIO;
|
|
|
|
ospeed = plcomspeed(t->c_ospeed, sc->sc_frequency);
|
|
|
|
/* Check requested parameters. */
|
|
if (ospeed < 0)
|
|
return EINVAL;
|
|
if (t->c_ispeed && t->c_ispeed != t->c_ospeed)
|
|
return EINVAL;
|
|
|
|
/*
|
|
* For the console, always force CLOCAL and !HUPCL, so that the port
|
|
* is always active.
|
|
*/
|
|
if (ISSET(sc->sc_swflags, TIOCFLAG_SOFTCAR) ||
|
|
ISSET(sc->sc_hwflags, PLCOM_HW_CONSOLE)) {
|
|
SET(t->c_cflag, CLOCAL);
|
|
CLR(t->c_cflag, HUPCL);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
lcr = ISSET(sc->sc_lcr, LCR_BRK) | cflag2lcr(t->c_cflag);
|
|
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
|
|
sc->sc_lcr = lcr;
|
|
|
|
/*
|
|
* PL010 has a fixed-length FIFO trigger point.
|
|
*/
|
|
if (ISSET(sc->sc_hwflags, PLCOM_HW_FIFO))
|
|
sc->sc_fifo = 1;
|
|
else
|
|
sc->sc_fifo = 0;
|
|
|
|
if (sc->sc_fifo)
|
|
SET(sc->sc_lcr, LCR_FEN);
|
|
|
|
/*
|
|
* If we're not in a mode that assumes a connection is present, then
|
|
* ignore carrier changes.
|
|
*/
|
|
if (ISSET(t->c_cflag, CLOCAL | MDMBUF))
|
|
sc->sc_msr_dcd = 0;
|
|
else
|
|
sc->sc_msr_dcd = MSR_DCD;
|
|
/*
|
|
* Set the flow control pins depending on the current flow control
|
|
* mode.
|
|
*/
|
|
if (ISSET(t->c_cflag, CRTSCTS)) {
|
|
sc->sc_mcr_dtr = MCR_DTR;
|
|
sc->sc_mcr_rts = MCR_RTS;
|
|
sc->sc_msr_cts = MSR_CTS;
|
|
} else if (ISSET(t->c_cflag, MDMBUF)) {
|
|
/*
|
|
* For DTR/DCD flow control, make sure we don't toggle DTR for
|
|
* carrier detection.
|
|
*/
|
|
sc->sc_mcr_dtr = 0;
|
|
sc->sc_mcr_rts = MCR_DTR;
|
|
sc->sc_msr_cts = MSR_DCD;
|
|
} else {
|
|
/*
|
|
* If no flow control, then always set RTS. This will make
|
|
* the other side happy if it mistakenly thinks we're doing
|
|
* RTS/CTS flow control.
|
|
*/
|
|
sc->sc_mcr_dtr = MCR_DTR | MCR_RTS;
|
|
sc->sc_mcr_rts = 0;
|
|
sc->sc_msr_cts = 0;
|
|
if (ISSET(sc->sc_mcr, MCR_DTR))
|
|
SET(sc->sc_mcr, MCR_RTS);
|
|
else
|
|
CLR(sc->sc_mcr, MCR_RTS);
|
|
}
|
|
sc->sc_msr_mask = sc->sc_msr_cts | sc->sc_msr_dcd;
|
|
|
|
#if 0
|
|
if (ospeed == 0)
|
|
CLR(sc->sc_mcr, sc->sc_mcr_dtr);
|
|
else
|
|
SET(sc->sc_mcr, sc->sc_mcr_dtr);
|
|
#endif
|
|
|
|
sc->sc_dlbl = ospeed;
|
|
sc->sc_dlbh = ospeed >> 8;
|
|
|
|
/* And copy to tty. */
|
|
tp->t_ispeed = 0;
|
|
tp->t_ospeed = t->c_ospeed;
|
|
tp->t_cflag = t->c_cflag;
|
|
|
|
if (!sc->sc_heldchange) {
|
|
if (sc->sc_tx_busy) {
|
|
sc->sc_heldtbc = sc->sc_tbc;
|
|
sc->sc_tbc = 0;
|
|
sc->sc_heldchange = 1;
|
|
} else
|
|
plcom_loadchannelregs(sc);
|
|
}
|
|
|
|
if (!ISSET(t->c_cflag, CHWFLOW)) {
|
|
/* Disable the high water mark. */
|
|
sc->sc_r_hiwat = 0;
|
|
sc->sc_r_lowat = 0;
|
|
if (ISSET(sc->sc_rx_flags, RX_TTY_OVERFLOWED)) {
|
|
CLR(sc->sc_rx_flags, RX_TTY_OVERFLOWED);
|
|
plcom_schedrx(sc);
|
|
}
|
|
if (ISSET(sc->sc_rx_flags, RX_TTY_BLOCKED|RX_IBUF_BLOCKED)) {
|
|
CLR(sc->sc_rx_flags, RX_TTY_BLOCKED|RX_IBUF_BLOCKED);
|
|
plcom_hwiflow(sc);
|
|
}
|
|
} else {
|
|
sc->sc_r_hiwat = plcom_rbuf_hiwat;
|
|
sc->sc_r_lowat = plcom_rbuf_lowat;
|
|
}
|
|
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
|
|
/*
|
|
* Update the tty layer's idea of the carrier bit, in case we changed
|
|
* CLOCAL or MDMBUF. We don't hang up here; we only do that by
|
|
* explicit request.
|
|
*/
|
|
(void) (*tp->t_linesw->l_modem)(tp, ISSET(sc->sc_msr, MSR_DCD));
|
|
|
|
#ifdef PLCOM_DEBUG
|
|
if (plcom_debug)
|
|
plcomstatus(sc, "plcomparam ");
|
|
#endif
|
|
|
|
if (!ISSET(t->c_cflag, CHWFLOW)) {
|
|
if (sc->sc_tx_stopped) {
|
|
sc->sc_tx_stopped = 0;
|
|
plcomstart(tp);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
plcom_iflush(struct plcom_softc *sc)
|
|
{
|
|
bus_space_tag_t iot = sc->sc_iot;
|
|
bus_space_handle_t ioh = sc->sc_ioh;
|
|
#ifdef DIAGNOSTIC
|
|
int reg;
|
|
#endif
|
|
int timo;
|
|
|
|
#ifdef DIAGNOSTIC
|
|
reg = 0xffff;
|
|
#endif
|
|
timo = 50000;
|
|
/* flush any pending I/O */
|
|
while (! ISSET(bus_space_read_1(iot, ioh, plcom_fr), FR_RXFE)
|
|
&& --timo)
|
|
#ifdef DIAGNOSTIC
|
|
reg =
|
|
#else
|
|
(void)
|
|
#endif
|
|
bus_space_read_1(iot, ioh, plcom_dr);
|
|
#ifdef DIAGNOSTIC
|
|
if (!timo)
|
|
printf("%s: plcom_iflush timeout %02x\n", sc->sc_dev.dv_xname,
|
|
reg);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
plcom_loadchannelregs(struct plcom_softc *sc)
|
|
{
|
|
bus_space_tag_t iot = sc->sc_iot;
|
|
bus_space_handle_t ioh = sc->sc_ioh;
|
|
|
|
/* XXXXX necessary? */
|
|
plcom_iflush(sc);
|
|
|
|
bus_space_write_1(iot, ioh, plcom_cr, 0);
|
|
|
|
bus_space_write_1(iot, ioh, plcom_dlbl, sc->sc_dlbl);
|
|
bus_space_write_1(iot, ioh, plcom_dlbh, sc->sc_dlbh);
|
|
bus_space_write_1(iot, ioh, plcom_lcr, sc->sc_lcr);
|
|
sc->sc_set_mcr(sc->sc_set_mcr_arg, sc->sc_dev.dv_unit,
|
|
sc->sc_mcr_active = sc->sc_mcr);
|
|
|
|
bus_space_write_1(iot, ioh, plcom_cr, sc->sc_cr);
|
|
}
|
|
|
|
int
|
|
plcomhwiflow(struct tty *tp, int block)
|
|
{
|
|
struct plcom_softc *sc = device_lookup(&plcom_cd, PLCOMUNIT(tp->t_dev));
|
|
int s;
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
return 0;
|
|
|
|
if (sc->sc_mcr_rts == 0)
|
|
return 0;
|
|
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
|
|
if (block) {
|
|
if (!ISSET(sc->sc_rx_flags, RX_TTY_BLOCKED)) {
|
|
SET(sc->sc_rx_flags, RX_TTY_BLOCKED);
|
|
plcom_hwiflow(sc);
|
|
}
|
|
} else {
|
|
if (ISSET(sc->sc_rx_flags, RX_TTY_OVERFLOWED)) {
|
|
CLR(sc->sc_rx_flags, RX_TTY_OVERFLOWED);
|
|
plcom_schedrx(sc);
|
|
}
|
|
if (ISSET(sc->sc_rx_flags, RX_TTY_BLOCKED)) {
|
|
CLR(sc->sc_rx_flags, RX_TTY_BLOCKED);
|
|
plcom_hwiflow(sc);
|
|
}
|
|
}
|
|
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* (un)block input via hw flowcontrol
|
|
*/
|
|
void
|
|
plcom_hwiflow(struct plcom_softc *sc)
|
|
{
|
|
if (sc->sc_mcr_rts == 0)
|
|
return;
|
|
|
|
if (ISSET(sc->sc_rx_flags, RX_ANY_BLOCK)) {
|
|
CLR(sc->sc_mcr, sc->sc_mcr_rts);
|
|
CLR(sc->sc_mcr_active, sc->sc_mcr_rts);
|
|
} else {
|
|
SET(sc->sc_mcr, sc->sc_mcr_rts);
|
|
SET(sc->sc_mcr_active, sc->sc_mcr_rts);
|
|
}
|
|
sc->sc_set_mcr(sc->sc_set_mcr_arg, sc->sc_dev.dv_unit,
|
|
sc->sc_mcr_active);
|
|
}
|
|
|
|
|
|
void
|
|
plcomstart(struct tty *tp)
|
|
{
|
|
struct plcom_softc *sc = device_lookup(&plcom_cd, PLCOMUNIT(tp->t_dev));
|
|
bus_space_tag_t iot = sc->sc_iot;
|
|
bus_space_handle_t ioh = sc->sc_ioh;
|
|
int s;
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
return;
|
|
|
|
s = spltty();
|
|
if (ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP))
|
|
goto out;
|
|
if (sc->sc_tx_stopped)
|
|
goto out;
|
|
|
|
if (tp->t_outq.c_cc <= tp->t_lowat) {
|
|
if (ISSET(tp->t_state, TS_ASLEEP)) {
|
|
CLR(tp->t_state, TS_ASLEEP);
|
|
wakeup(&tp->t_outq);
|
|
}
|
|
selwakeup(&tp->t_wsel);
|
|
if (tp->t_outq.c_cc == 0)
|
|
goto out;
|
|
}
|
|
|
|
/* Grab the first contiguous region of buffer space. */
|
|
{
|
|
u_char *tba;
|
|
int tbc;
|
|
|
|
tba = tp->t_outq.c_cf;
|
|
tbc = ndqb(&tp->t_outq, 0);
|
|
|
|
(void)splserial();
|
|
PLCOM_LOCK(sc);
|
|
|
|
sc->sc_tba = tba;
|
|
sc->sc_tbc = tbc;
|
|
}
|
|
|
|
SET(tp->t_state, TS_BUSY);
|
|
sc->sc_tx_busy = 1;
|
|
|
|
/* Enable transmit completion interrupts if necessary. */
|
|
if (!ISSET(sc->sc_cr, CR_TIE)) {
|
|
SET(sc->sc_cr, CR_TIE);
|
|
bus_space_write_1(iot, ioh, plcom_cr, sc->sc_cr);
|
|
}
|
|
|
|
/* Output the first chunk of the contiguous buffer. */
|
|
{
|
|
int n;
|
|
|
|
n = sc->sc_tbc;
|
|
if (n > sc->sc_fifolen)
|
|
n = sc->sc_fifolen;
|
|
bus_space_write_multi_1(iot, ioh, plcom_dr, sc->sc_tba, n);
|
|
sc->sc_tbc -= n;
|
|
sc->sc_tba += n;
|
|
}
|
|
PLCOM_UNLOCK(sc);
|
|
out:
|
|
splx(s);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Stop output on a line.
|
|
*/
|
|
void
|
|
plcomstop(struct tty *tp, int flag)
|
|
{
|
|
struct plcom_softc *sc = device_lookup(&plcom_cd, PLCOMUNIT(tp->t_dev));
|
|
int s;
|
|
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
if (ISSET(tp->t_state, TS_BUSY)) {
|
|
/* Stop transmitting at the next chunk. */
|
|
sc->sc_tbc = 0;
|
|
sc->sc_heldtbc = 0;
|
|
if (!ISSET(tp->t_state, TS_TTSTOP))
|
|
SET(tp->t_state, TS_FLUSH);
|
|
}
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
plcomdiag(void *arg)
|
|
{
|
|
struct plcom_softc *sc = arg;
|
|
int overflows, floods;
|
|
int s;
|
|
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
overflows = sc->sc_overflows;
|
|
sc->sc_overflows = 0;
|
|
floods = sc->sc_floods;
|
|
sc->sc_floods = 0;
|
|
sc->sc_errors = 0;
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
|
|
log(LOG_WARNING, "%s: %d silo overflow%s, %d ibuf flood%s\n",
|
|
sc->sc_dev.dv_xname,
|
|
overflows, overflows == 1 ? "" : "s",
|
|
floods, floods == 1 ? "" : "s");
|
|
}
|
|
|
|
integrate void
|
|
plcom_rxsoft(struct plcom_softc *sc, struct tty *tp)
|
|
{
|
|
int (*rint) (int, struct tty *) = tp->t_linesw->l_rint;
|
|
u_char *get, *end;
|
|
u_int cc, scc;
|
|
u_char rsr;
|
|
int code;
|
|
int s;
|
|
|
|
end = sc->sc_ebuf;
|
|
get = sc->sc_rbget;
|
|
scc = cc = plcom_rbuf_size - sc->sc_rbavail;
|
|
|
|
if (cc == plcom_rbuf_size) {
|
|
sc->sc_floods++;
|
|
if (sc->sc_errors++ == 0)
|
|
callout_reset(&sc->sc_diag_callout, 60 * hz,
|
|
plcomdiag, sc);
|
|
}
|
|
|
|
while (cc) {
|
|
code = get[0];
|
|
rsr = get[1];
|
|
if (ISSET(rsr, RSR_OE | RSR_BE | RSR_FE | RSR_PE)) {
|
|
if (ISSET(rsr, RSR_OE)) {
|
|
sc->sc_overflows++;
|
|
if (sc->sc_errors++ == 0)
|
|
callout_reset(&sc->sc_diag_callout,
|
|
60 * hz, plcomdiag, sc);
|
|
}
|
|
if (ISSET(rsr, RSR_BE | RSR_FE))
|
|
SET(code, TTY_FE);
|
|
if (ISSET(rsr, RSR_PE))
|
|
SET(code, TTY_PE);
|
|
}
|
|
if ((*rint)(code, tp) == -1) {
|
|
/*
|
|
* The line discipline's buffer is out of space.
|
|
*/
|
|
if (!ISSET(sc->sc_rx_flags, RX_TTY_BLOCKED)) {
|
|
/*
|
|
* We're either not using flow control, or the
|
|
* line discipline didn't tell us to block for
|
|
* some reason. Either way, we have no way to
|
|
* know when there's more space available, so
|
|
* just drop the rest of the data.
|
|
*/
|
|
get += cc << 1;
|
|
if (get >= end)
|
|
get -= plcom_rbuf_size << 1;
|
|
cc = 0;
|
|
} else {
|
|
/*
|
|
* Don't schedule any more receive processing
|
|
* until the line discipline tells us there's
|
|
* space available (through plcomhwiflow()).
|
|
* Leave the rest of the data in the input
|
|
* buffer.
|
|
*/
|
|
SET(sc->sc_rx_flags, RX_TTY_OVERFLOWED);
|
|
}
|
|
break;
|
|
}
|
|
get += 2;
|
|
if (get >= end)
|
|
get = sc->sc_rbuf;
|
|
cc--;
|
|
}
|
|
|
|
if (cc != scc) {
|
|
sc->sc_rbget = get;
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
|
|
cc = sc->sc_rbavail += scc - cc;
|
|
/* Buffers should be ok again, release possible block. */
|
|
if (cc >= sc->sc_r_lowat) {
|
|
if (ISSET(sc->sc_rx_flags, RX_IBUF_OVERFLOWED)) {
|
|
CLR(sc->sc_rx_flags, RX_IBUF_OVERFLOWED);
|
|
SET(sc->sc_cr, CR_RIE | CR_RTIE);
|
|
bus_space_write_1(sc->sc_iot, sc->sc_ioh, plcom_cr, sc->sc_cr);
|
|
}
|
|
if (ISSET(sc->sc_rx_flags, RX_IBUF_BLOCKED)) {
|
|
CLR(sc->sc_rx_flags, RX_IBUF_BLOCKED);
|
|
plcom_hwiflow(sc);
|
|
}
|
|
}
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
}
|
|
}
|
|
|
|
integrate void
|
|
plcom_txsoft(struct plcom_softc *sc, struct tty *tp)
|
|
{
|
|
|
|
CLR(tp->t_state, TS_BUSY);
|
|
if (ISSET(tp->t_state, TS_FLUSH))
|
|
CLR(tp->t_state, TS_FLUSH);
|
|
else
|
|
ndflush(&tp->t_outq, (int)(sc->sc_tba - tp->t_outq.c_cf));
|
|
(*tp->t_linesw->l_start)(tp);
|
|
}
|
|
|
|
integrate void
|
|
plcom_stsoft(struct plcom_softc *sc, struct tty *tp)
|
|
{
|
|
u_char msr, delta;
|
|
int s;
|
|
|
|
s = splserial();
|
|
PLCOM_LOCK(sc);
|
|
msr = sc->sc_msr;
|
|
delta = sc->sc_msr_delta;
|
|
sc->sc_msr_delta = 0;
|
|
PLCOM_UNLOCK(sc);
|
|
splx(s);
|
|
|
|
if (ISSET(delta, sc->sc_msr_dcd)) {
|
|
/*
|
|
* Inform the tty layer that carrier detect changed.
|
|
*/
|
|
(void) (*tp->t_linesw->l_modem)(tp, ISSET(msr, MSR_DCD));
|
|
}
|
|
|
|
if (ISSET(delta, sc->sc_msr_cts)) {
|
|
/* Block or unblock output according to flow control. */
|
|
if (ISSET(msr, sc->sc_msr_cts)) {
|
|
sc->sc_tx_stopped = 0;
|
|
(*tp->t_linesw->l_start)(tp);
|
|
} else {
|
|
sc->sc_tx_stopped = 1;
|
|
}
|
|
}
|
|
|
|
#ifdef PLCOM_DEBUG
|
|
if (plcom_debug)
|
|
plcomstatus(sc, "plcom_stsoft");
|
|
#endif
|
|
}
|
|
|
|
#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS
|
|
void
|
|
plcomsoft(void *arg)
|
|
{
|
|
struct plcom_softc *sc = arg;
|
|
struct tty *tp;
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
return;
|
|
|
|
{
|
|
#else
|
|
void
|
|
#ifndef __NO_SOFT_SERIAL_INTERRUPT
|
|
plcomsoft(void)
|
|
#else
|
|
plcomsoft(void *arg)
|
|
#endif
|
|
{
|
|
struct plcom_softc *sc;
|
|
struct tty *tp;
|
|
int unit;
|
|
#ifdef __NO_SOFT_SERIAL_INTERRUPT
|
|
int s;
|
|
|
|
s = splsoftserial();
|
|
plcom_softintr_scheduled = 0;
|
|
#endif
|
|
|
|
for (unit = 0; unit < plcom_cd.cd_ndevs; unit++) {
|
|
sc = device_lookup(&plcom_cd, unit);
|
|
if (sc == NULL || !ISSET(sc->sc_hwflags, PLCOM_HW_DEV_OK))
|
|
continue;
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
continue;
|
|
|
|
tp = sc->sc_tty;
|
|
if (tp == NULL)
|
|
continue;
|
|
if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0)
|
|
continue;
|
|
#endif
|
|
tp = sc->sc_tty;
|
|
|
|
if (sc->sc_rx_ready) {
|
|
sc->sc_rx_ready = 0;
|
|
plcom_rxsoft(sc, tp);
|
|
}
|
|
|
|
if (sc->sc_st_check) {
|
|
sc->sc_st_check = 0;
|
|
plcom_stsoft(sc, tp);
|
|
}
|
|
|
|
if (sc->sc_tx_done) {
|
|
sc->sc_tx_done = 0;
|
|
plcom_txsoft(sc, tp);
|
|
}
|
|
}
|
|
|
|
#ifndef __HAVE_GENERIC_SOFT_INTERRUPTS
|
|
#ifdef __NO_SOFT_SERIAL_INTERRUPT
|
|
splx(s);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
#ifdef __ALIGN_BRACKET_LEVEL_FOR_CTAGS
|
|
/* there has got to be a better way to do plcomsoft() */
|
|
}}
|
|
#endif
|
|
|
|
int
|
|
plcomintr(void *arg)
|
|
{
|
|
struct plcom_softc *sc = arg;
|
|
bus_space_tag_t iot = sc->sc_iot;
|
|
bus_space_handle_t ioh = sc->sc_ioh;
|
|
u_char *put, *end;
|
|
u_int cc;
|
|
u_char rsr, iir;
|
|
|
|
if (PLCOM_ISALIVE(sc) == 0)
|
|
return 0;
|
|
|
|
PLCOM_LOCK(sc);
|
|
iir = bus_space_read_1(iot, ioh, plcom_iir);
|
|
if (! ISSET(iir, IIR_IMASK)) {
|
|
PLCOM_UNLOCK(sc);
|
|
return 0;
|
|
}
|
|
|
|
end = sc->sc_ebuf;
|
|
put = sc->sc_rbput;
|
|
cc = sc->sc_rbavail;
|
|
|
|
do {
|
|
u_char msr, delta, fr;
|
|
|
|
fr = bus_space_read_1(iot, ioh, plcom_fr);
|
|
|
|
if (!ISSET(fr, FR_RXFE) &&
|
|
!ISSET(sc->sc_rx_flags, RX_IBUF_OVERFLOWED)) {
|
|
while (cc > 0) {
|
|
int cn_trapped = 0;
|
|
put[0] = bus_space_read_1(iot, ioh,
|
|
plcom_dr);
|
|
rsr = bus_space_read_1(iot, ioh, plcom_rsr);
|
|
/* Clear any error status. */
|
|
if (ISSET(rsr,
|
|
(RSR_BE | RSR_OE | RSR_PE | RSR_FE)))
|
|
bus_space_write_1(iot, ioh, plcom_ecr,
|
|
0);
|
|
if (ISSET(rsr, RSR_BE)) {
|
|
int cn_trapped = 0;
|
|
cn_check_magic(sc->sc_tty->t_dev,
|
|
CNC_BREAK, plcom_cnm_state);
|
|
if (cn_trapped)
|
|
continue;
|
|
#if defined(KGDB)
|
|
if (ISSET(sc->sc_hwflags,
|
|
PLCOM_HW_KGDB)) {
|
|
kgdb_connect(1);
|
|
continue;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
put[1] = rsr;
|
|
cn_check_magic(sc->sc_tty->t_dev,
|
|
put[0], plcom_cnm_state);
|
|
if (cn_trapped) {
|
|
fr = bus_space_read_1(iot, ioh,
|
|
plcom_fr);
|
|
if (ISSET(fr, FR_RXFE))
|
|
break;
|
|
|
|
continue;
|
|
}
|
|
put += 2;
|
|
if (put >= end)
|
|
put = sc->sc_rbuf;
|
|
cc--;
|
|
|
|
fr = bus_space_read_1(iot, ioh, plcom_fr);
|
|
if (ISSET(fr, FR_RXFE))
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Current string of incoming characters ended because
|
|
* no more data was available or we ran out of space.
|
|
* Schedule a receive event if any data was received.
|
|
* If we're out of space, turn off receive interrupts.
|
|
*/
|
|
sc->sc_rbput = put;
|
|
sc->sc_rbavail = cc;
|
|
if (!ISSET(sc->sc_rx_flags, RX_TTY_OVERFLOWED))
|
|
sc->sc_rx_ready = 1;
|
|
|
|
/*
|
|
* See if we are in danger of overflowing a buffer. If
|
|
* so, use hardware flow control to ease the pressure.
|
|
*/
|
|
if (!ISSET(sc->sc_rx_flags, RX_IBUF_BLOCKED) &&
|
|
cc < sc->sc_r_hiwat) {
|
|
SET(sc->sc_rx_flags, RX_IBUF_BLOCKED);
|
|
plcom_hwiflow(sc);
|
|
}
|
|
|
|
/*
|
|
* If we're out of space, disable receive interrupts
|
|
* until the queue has drained a bit.
|
|
*/
|
|
if (!cc) {
|
|
SET(sc->sc_rx_flags, RX_IBUF_OVERFLOWED);
|
|
CLR(sc->sc_cr, CR_RIE | CR_RTIE);
|
|
bus_space_write_1(iot, ioh, plcom_cr,
|
|
sc->sc_cr);
|
|
}
|
|
} else {
|
|
if (ISSET(iir, IIR_RIS)) {
|
|
bus_space_write_1(iot, ioh, plcom_cr, 0);
|
|
delay(10);
|
|
bus_space_write_1(iot, ioh, plcom_cr,
|
|
sc->sc_cr);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
msr = bus_space_read_1(iot, ioh, plcom_fr);
|
|
delta = msr ^ sc->sc_msr;
|
|
sc->sc_msr = msr;
|
|
/* Clear any pending modem status interrupt. */
|
|
if (iir & IIR_MIS)
|
|
bus_space_write_1(iot, ioh, plcom_icr, 0);
|
|
/*
|
|
* Pulse-per-second (PSS) signals on edge of DCD?
|
|
* Process these even if line discipline is ignoring DCD.
|
|
*/
|
|
if (delta & sc->sc_ppsmask) {
|
|
struct timeval tv;
|
|
if ((msr & sc->sc_ppsmask) == sc->sc_ppsassert) {
|
|
/* XXX nanotime() */
|
|
microtime(&tv);
|
|
TIMEVAL_TO_TIMESPEC(&tv,
|
|
&sc->ppsinfo.assert_timestamp);
|
|
if (sc->ppsparam.mode & PPS_OFFSETASSERT) {
|
|
timespecadd(&sc->ppsinfo.assert_timestamp,
|
|
&sc->ppsparam.assert_offset,
|
|
&sc->ppsinfo.assert_timestamp);
|
|
}
|
|
|
|
#ifdef PPS_SYNC
|
|
if (sc->ppsparam.mode & PPS_HARDPPSONASSERT)
|
|
hardpps(&tv, tv.tv_usec);
|
|
#endif
|
|
sc->ppsinfo.assert_sequence++;
|
|
sc->ppsinfo.current_mode = sc->ppsparam.mode;
|
|
|
|
} else if ((msr & sc->sc_ppsmask) == sc->sc_ppsclear) {
|
|
/* XXX nanotime() */
|
|
microtime(&tv);
|
|
TIMEVAL_TO_TIMESPEC(&tv,
|
|
&sc->ppsinfo.clear_timestamp);
|
|
if (sc->ppsparam.mode & PPS_OFFSETCLEAR) {
|
|
timespecadd(&sc->ppsinfo.clear_timestamp,
|
|
&sc->ppsparam.clear_offset,
|
|
&sc->ppsinfo.clear_timestamp);
|
|
}
|
|
|
|
#ifdef PPS_SYNC
|
|
if (sc->ppsparam.mode & PPS_HARDPPSONCLEAR)
|
|
hardpps(&tv, tv.tv_usec);
|
|
#endif
|
|
sc->ppsinfo.clear_sequence++;
|
|
sc->ppsinfo.current_mode = sc->ppsparam.mode;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Process normal status changes
|
|
*/
|
|
if (ISSET(delta, sc->sc_msr_mask)) {
|
|
SET(sc->sc_msr_delta, delta);
|
|
|
|
/*
|
|
* Stop output immediately if we lose the output
|
|
* flow control signal or carrier detect.
|
|
*/
|
|
if (ISSET(~msr, sc->sc_msr_mask)) {
|
|
sc->sc_tbc = 0;
|
|
sc->sc_heldtbc = 0;
|
|
#ifdef PLCOM_DEBUG
|
|
if (plcom_debug)
|
|
plcomstatus(sc, "plcomintr ");
|
|
#endif
|
|
}
|
|
|
|
sc->sc_st_check = 1;
|
|
}
|
|
|
|
/*
|
|
* Done handling any receive interrupts. See if data
|
|
* can be * transmitted as well. Schedule tx done
|
|
* event if no data left * and tty was marked busy.
|
|
*/
|
|
if (ISSET(iir, IIR_TIS)) {
|
|
/*
|
|
* If we've delayed a parameter change, do it
|
|
* now, and restart * output.
|
|
*/
|
|
if (sc->sc_heldchange) {
|
|
plcom_loadchannelregs(sc);
|
|
sc->sc_heldchange = 0;
|
|
sc->sc_tbc = sc->sc_heldtbc;
|
|
sc->sc_heldtbc = 0;
|
|
}
|
|
|
|
/*
|
|
* Output the next chunk of the contiguous
|
|
* buffer, if any.
|
|
*/
|
|
if (sc->sc_tbc > 0) {
|
|
int n;
|
|
|
|
n = sc->sc_tbc;
|
|
if (n > sc->sc_fifolen)
|
|
n = sc->sc_fifolen;
|
|
bus_space_write_multi_1(iot, ioh, plcom_dr,
|
|
sc->sc_tba, n);
|
|
sc->sc_tbc -= n;
|
|
sc->sc_tba += n;
|
|
} else {
|
|
/*
|
|
* Disable transmit plcompletion
|
|
* interrupts if necessary.
|
|
*/
|
|
if (ISSET(sc->sc_cr, CR_TIE)) {
|
|
CLR(sc->sc_cr, CR_TIE);
|
|
bus_space_write_1(iot, ioh, plcom_cr,
|
|
sc->sc_cr);
|
|
}
|
|
if (sc->sc_tx_busy) {
|
|
sc->sc_tx_busy = 0;
|
|
sc->sc_tx_done = 1;
|
|
}
|
|
}
|
|
}
|
|
} while (ISSET((iir = bus_space_read_1(iot, ioh, plcom_iir)),
|
|
IIR_IMASK));
|
|
|
|
PLCOM_UNLOCK(sc);
|
|
|
|
/* Wake up the poller. */
|
|
#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS
|
|
softintr_schedule(sc->sc_si);
|
|
#else
|
|
#ifndef __NO_SOFT_SERIAL_INTERRUPT
|
|
setsoftserial();
|
|
#else
|
|
if (!plcom_softintr_scheduled) {
|
|
plcom_softintr_scheduled = 1;
|
|
callout_reset(&plcomsoft_callout, 1, plcomsoft, NULL);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if NRND > 0 && defined(RND_COM)
|
|
rnd_add_uint32(&sc->rnd_source, iir | rsr);
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* The following functions are polled getc and putc routines, shared
|
|
* by the console and kgdb glue.
|
|
*
|
|
* The read-ahead code is so that you can detect pending in-band
|
|
* cn_magic in polled mode while doing output rather than having to
|
|
* wait until the kernel decides it needs input.
|
|
*/
|
|
|
|
#define MAX_READAHEAD 20
|
|
static int plcom_readahead[MAX_READAHEAD];
|
|
static int plcom_readaheadcount = 0;
|
|
|
|
int
|
|
plcom_common_getc(dev_t dev, bus_space_tag_t iot, bus_space_handle_t ioh)
|
|
{
|
|
int s = splserial();
|
|
u_char stat, c;
|
|
|
|
/* got a character from reading things earlier */
|
|
if (plcom_readaheadcount > 0) {
|
|
int i;
|
|
|
|
c = plcom_readahead[0];
|
|
for (i = 1; i < plcom_readaheadcount; i++) {
|
|
plcom_readahead[i-1] = plcom_readahead[i];
|
|
}
|
|
plcom_readaheadcount--;
|
|
splx(s);
|
|
return c;
|
|
}
|
|
|
|
/* block until a character becomes available */
|
|
while (ISSET(stat = bus_space_read_1(iot, ioh, plcom_fr), FR_RXFE))
|
|
;
|
|
|
|
c = bus_space_read_1(iot, ioh, plcom_dr);
|
|
stat = bus_space_read_1(iot, ioh, plcom_iir);
|
|
{
|
|
int cn_trapped = 0; /* unused */
|
|
#ifdef DDB
|
|
extern int db_active;
|
|
if (!db_active)
|
|
#endif
|
|
cn_check_magic(dev, c, plcom_cnm_state);
|
|
}
|
|
splx(s);
|
|
return c;
|
|
}
|
|
|
|
void
|
|
plcom_common_putc(dev_t dev, bus_space_tag_t iot, bus_space_handle_t ioh,
|
|
int c)
|
|
{
|
|
int s = splserial();
|
|
int timo;
|
|
|
|
int cin, stat;
|
|
if (plcom_readaheadcount < MAX_READAHEAD
|
|
&& !ISSET(stat = bus_space_read_1(iot, ioh, plcom_fr), FR_RXFE)) {
|
|
int cn_trapped = 0;
|
|
cin = bus_space_read_1(iot, ioh, plcom_dr);
|
|
stat = bus_space_read_1(iot, ioh, plcom_iir);
|
|
cn_check_magic(dev, cin, plcom_cnm_state);
|
|
plcom_readahead[plcom_readaheadcount++] = cin;
|
|
}
|
|
|
|
/* wait for any pending transmission to finish */
|
|
timo = 150000;
|
|
while (!ISSET(bus_space_read_1(iot, ioh, plcom_fr), FR_TXFE) && --timo)
|
|
continue;
|
|
|
|
bus_space_write_1(iot, ioh, plcom_dr, c);
|
|
PLCOM_BARRIER(iot, ioh, BR | BW);
|
|
|
|
/* wait for this transmission to complete */
|
|
timo = 1500000;
|
|
while (!ISSET(bus_space_read_1(iot, ioh, plcom_fr), FR_TXFE) && --timo)
|
|
continue;
|
|
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* Initialize UART for use as console or KGDB line.
|
|
*/
|
|
int
|
|
plcominit(bus_space_tag_t iot, bus_addr_t iobase, int rate, int frequency,
|
|
tcflag_t cflag, bus_space_handle_t *iohp)
|
|
{
|
|
bus_space_handle_t ioh;
|
|
|
|
if (bus_space_map(iot, iobase, PLCOM_UART_SIZE, 0, &ioh))
|
|
return ENOMEM; /* ??? */
|
|
|
|
rate = plcomspeed(rate, frequency);
|
|
bus_space_write_1(iot, ioh, plcom_cr, 0);
|
|
bus_space_write_1(iot, ioh, plcom_dlbl, rate);
|
|
bus_space_write_1(iot, ioh, plcom_dlbh, rate >> 8);
|
|
bus_space_write_1(iot, ioh, plcom_lcr, cflag2lcr(cflag) | LCR_FEN);
|
|
bus_space_write_1(iot, ioh, plcom_cr, CR_UARTEN);
|
|
|
|
#if 0
|
|
/* Ought to do something like this, but we have no sc to
|
|
dereference. */
|
|
sc->sc_set_mcr(sc->sc_set_mcr_arg, sc->sc_dev.dv_unit,
|
|
MCR_DTR | MCR_RTS);
|
|
#endif
|
|
|
|
*iohp = ioh;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Following are all routines needed for PLCOM to act as console
|
|
*/
|
|
struct consdev plcomcons = {
|
|
NULL, NULL, plcomcngetc, plcomcnputc, plcomcnpollc, NULL,
|
|
NODEV, CN_NORMAL
|
|
};
|
|
|
|
|
|
int
|
|
plcomcnattach(bus_space_tag_t iot, bus_addr_t iobase, int rate, int frequency,
|
|
tcflag_t cflag, int unit)
|
|
{
|
|
int res;
|
|
|
|
res = plcominit(iot, iobase, rate, frequency, cflag, &plcomconsioh);
|
|
if (res)
|
|
return res;
|
|
|
|
cn_tab = &plcomcons;
|
|
cn_init_magic(&plcom_cnm_state);
|
|
cn_set_magic("\047\001"); /* default magic is BREAK */
|
|
|
|
plcomconstag = iot;
|
|
plcomconsunit = unit;
|
|
plcomconsrate = rate;
|
|
plcomconscflag = cflag;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
plcomcndetach(void)
|
|
{
|
|
bus_space_unmap(plcomconstag, plcomconsioh, PLCOM_UART_SIZE);
|
|
plcomconstag = NULL;
|
|
|
|
cn_tab = NULL;
|
|
}
|
|
|
|
int
|
|
plcomcngetc(dev_t dev)
|
|
{
|
|
return plcom_common_getc(dev, plcomconstag, plcomconsioh);
|
|
}
|
|
|
|
/*
|
|
* Console kernel output character routine.
|
|
*/
|
|
void
|
|
plcomcnputc(dev_t dev, int c)
|
|
{
|
|
plcom_common_putc(dev, plcomconstag, plcomconsioh, c);
|
|
}
|
|
|
|
void
|
|
plcomcnpollc(dev_t dev, int on)
|
|
{
|
|
|
|
}
|
|
|
|
#ifdef KGDB
|
|
int
|
|
plcom_kgdb_attach(bus_space_tag_t iot, bus_addr_t iobase, int rate,
|
|
int frequency, tcflag_t cflag, int unit)
|
|
{
|
|
int res;
|
|
|
|
if (iot == plcomconstag && iobase == plcomconsunit)
|
|
return EBUSY; /* cannot share with console */
|
|
|
|
res = plcominit(iot, iobase, rate, frequency, cflag, &plcom_kgdb_ioh);
|
|
if (res)
|
|
return res;
|
|
|
|
kgdb_attach(plcom_kgdb_getc, plcom_kgdb_putc, NULL);
|
|
kgdb_dev = 123; /* unneeded, only to satisfy some tests */
|
|
|
|
plcom_kgdb_iot = iot;
|
|
plcom_kgdb_unit = unit;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
int
|
|
plcom_kgdb_getc(void *arg)
|
|
{
|
|
return plcom_common_getc(NODEV, plcom_kgdb_iot, plcom_kgdb_ioh);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
void
|
|
plcom_kgdb_putc(void *arg, int c)
|
|
{
|
|
plcom_common_putc(NODEV, plcom_kgdb_iot, plcom_kgdb_ioh, c);
|
|
}
|
|
#endif /* KGDB */
|
|
|
|
/* helper function to identify the plcom ports used by
|
|
console or KGDB (and not yet autoconf attached) */
|
|
int
|
|
plcom_is_console(bus_space_tag_t iot, int unit,
|
|
bus_space_handle_t *ioh)
|
|
{
|
|
bus_space_handle_t help;
|
|
|
|
if (!plcomconsattached &&
|
|
iot == plcomconstag && unit == plcomconsunit)
|
|
help = plcomconsioh;
|
|
#ifdef KGDB
|
|
else if (!plcom_kgdb_attached &&
|
|
iot == plcom_kgdb_iot && unit == plcom_kgdb_unit)
|
|
help = plcom_kgdb_ioh;
|
|
#endif
|
|
else
|
|
return 0;
|
|
|
|
if (ioh)
|
|
*ioh = help;
|
|
return 1;
|
|
}
|