#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: xlcom.c,v 1.13 2025/12/31 15:33:51 andvar Exp $");
#include "opt_kgdb.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/tty.h>
#include <sys/time.h>
#include <sys/syslog.h>
#include <sys/intr.h>
#include <sys/bus.h>
#if defined(KGDB)
#include <sys/kgdb.h>
#endif
#include <dev/cons.h>
#include <evbppc/virtex/virtex.h>
#include <evbppc/virtex/dev/xcvbusvar.h>
#include <evbppc/virtex/dev/xlcomreg.h>
#define XLCOM_UNIT_MASK 0x7f
#define XLCOM_DIALOUT_MASK 0x80
#define UNIT(dev) (minor(dev) & XLCOM_UNIT_MASK)
#define DIALOUT(dev) (minor(dev) & XLCOM_DIALOUT_MASK)
#define XLCOM_CHAR_PE 0x8000
#define XLCOM_CHAR_FE 0x4000
#define next(idx) (void)((idx) = ((idx) + 1) % XLCOM_RXBUF_SIZE)
#define XLCOM_RXBUF_SIZE 1024
struct xlcom_softc {
device_t sc_dev;
struct tty *sc_tty;
void *sc_ih;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
void *sc_rx_soft;
void *sc_tx_soft;
u_short sc_rbuf[XLCOM_RXBUF_SIZE];
volatile u_int sc_rput;
volatile u_int sc_rget;
volatile u_int sc_ravail;
u_char *sc_tba;
u_int sc_tbc;
};
static int xlcom_intr(void *);
static void xlcom_rx_soft(void *);
static void xlcom_tx_soft(void *);
static void xlcom_reset(bus_space_tag_t, bus_space_handle_t);
static int xlcom_busy_getc(bus_space_tag_t, bus_space_handle_t);
static void xlcom_busy_putc(bus_space_tag_t, bus_space_handle_t, int);
static int xlcom_cngetc(dev_t);
static void xlcom_cnputc(dev_t, int);
void xlcom_cninit(struct consdev *);
#if defined(KGDB)
void xlcom_kgdbinit(void);
static void xlcom_kgdb_putc(void *, int);
static int xlcom_kgdb_getc(void *);
#endif
static struct cnm_state xlcom_cnm_state;
struct consdev consdev_xlcom = {
.cn_probe = nullcnprobe,
.cn_init = xlcom_cninit,
.cn_getc = xlcom_cngetc,
.cn_putc = xlcom_cnputc,
.cn_pollc = nullcnpollc,
.cn_pri = CN_REMOTE
};
static dev_type_open(xlcom_open);
static dev_type_read(xlcom_read);
static dev_type_write(xlcom_write);
static dev_type_ioctl(xlcom_ioctl);
static dev_type_poll(xlcom_poll);
static dev_type_close(xlcom_close);
static dev_type_tty(xlcom_tty);
static dev_type_stop(xlcom_stop);
const struct cdevsw xlcom_cdevsw = {
.d_open = xlcom_open,
.d_close = xlcom_close,
.d_read = xlcom_read,
.d_write = xlcom_write,
.d_ioctl = xlcom_ioctl,
.d_stop = xlcom_stop,
.d_tty = xlcom_tty,
.d_poll = xlcom_poll,
.d_mmap = nommap,
.d_kqfilter = ttykqfilter,
.d_discard = nodiscard,
.d_flag = D_TTY
};
extern struct cfdriver xlcom_cd;
static int xlcom_param(struct tty *, struct termios *);
static void xlcom_start(struct tty *);
static void xlcom_attach(device_t, device_t, void *);
CFATTACH_DECL_NEW(xlcom, sizeof(struct xlcom_softc),
xcvbus_child_match, xlcom_attach, NULL, NULL);
static void
xlcom_attach(device_t parent, device_t self, void *aux)
{
struct xcvbus_attach_args *vaa = aux;
struct xlcom_softc *sc = device_private(self);
struct tty *tp;
dev_t dev;
aprint_normal(": UartLite serial port\n");
sc->sc_dev = self;
#if defined(KGDB)
if (sc->sc_iot == kgdb_iot && sc->sc_ioh == kgdb_ioh) {
aprint_error_dev(self, "already in use by kgdb\n");
return;
}
#endif
if ((sc->sc_ih = intr_establish(vaa->vaa_intr, IST_LEVEL, IPL_SERIAL,
xlcom_intr, sc)) == NULL) {
aprint_error_dev(self, "could not establish interrupt\n");
return ;
}
dev = makedev(cdevsw_lookup_major(&xlcom_cdevsw), device_unit(self));
if (cn_tab == &consdev_xlcom) {
cn_init_magic(&xlcom_cnm_state);
cn_set_magic("\x23\x2e");
cn_tab->cn_dev = dev;
sc->sc_iot = consdev_iot;
sc->sc_ioh = consdev_ioh;
aprint_normal_dev(self, "console\n");
} else {
sc->sc_iot = vaa->vaa_iot;
if (bus_space_map(vaa->vaa_iot, vaa->vaa_addr, XLCOM_SIZE, 0,
&sc->sc_ioh) != 0) {
aprint_error_dev(self, "could not map registers\n");
return;
}
xlcom_reset(sc->sc_iot, sc->sc_ioh);
}
sc->sc_tbc = 0;
sc->sc_tba = NULL;
sc->sc_rput = sc->sc_rget = 0;
sc->sc_ravail = XLCOM_RXBUF_SIZE;
sc->sc_rx_soft = softint_establish(SOFTINT_SERIAL, xlcom_rx_soft, sc);
sc->sc_tx_soft = softint_establish(SOFTINT_SERIAL, xlcom_tx_soft, sc);
if (sc->sc_rx_soft == NULL || sc->sc_tx_soft == NULL) {
aprint_error_dev(self,
"could not establish Rx or Tx softintr\n");
return;
}
tp = tty_alloc();
tp->t_dev = dev;
tp->t_oproc = xlcom_start;
tp->t_param = xlcom_param;
tp->t_hwiflow = NULL;
tty_attach(tp);
if (cn_tab == &consdev_xlcom) {
bus_space_write_4(sc->sc_iot, sc->sc_ioh, XLCOM_CNTL,
CNTL_INTR_EN);
}
sc->sc_tty = tp;
}
static void
xlcom_tx_soft(void *arg)
{
struct xlcom_softc *sc = (struct xlcom_softc *)arg;
struct tty *tp = sc->sc_tty;
if (tp->t_state & TS_FLUSH)
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);
}
static void
xlcom_rx_soft(void *arg)
{
struct xlcom_softc *sc = (struct xlcom_softc *)arg;
struct tty *tp = sc->sc_tty;
int (*rint)(int, struct tty *);
u_short c;
int d;
rint = tp->t_linesw->l_rint;
while (sc->sc_rput != sc->sc_rget) {
c = sc->sc_rbuf[sc->sc_rget];
next(sc->sc_rget);
sc->sc_ravail++;
d = (c & 0xff) |
((c & XLCOM_CHAR_PE) != 0 ? TTY_PE : 0) |
((c & XLCOM_CHAR_FE) != 0 ? TTY_FE : 0);
if ((rint)(d, tp) == -1) {
sc->sc_rget = sc->sc_rput;
return ;
}
}
}
static void
xlcom_send_chunk(struct xlcom_softc *sc)
{
uint32_t stat;
if (sc->sc_tbc <= 0) {
return ;
}
while (sc->sc_tbc > 0) {
stat = bus_space_read_4(sc->sc_iot, sc->sc_ioh, XLCOM_STAT);
if (stat & STAT_TX_FULL)
break;
bus_space_write_4(sc->sc_iot, sc->sc_ioh, XLCOM_TX_FIFO,
*sc->sc_tba);
sc->sc_tbc--;
sc->sc_tba++;
}
if (sc->sc_tbc == 0) {
sc->sc_tty->t_state &= ~TS_BUSY;
softint_schedule(sc->sc_tx_soft);
}
}
static void
xlcom_recv_chunk(struct xlcom_softc *sc)
{
uint32_t stat;
u_short c;
u_int n;
n = sc->sc_ravail;
stat = bus_space_read_4(sc->sc_iot, sc->sc_ioh, XLCOM_STAT);
while ((stat & STAT_RX_DATA) != 0 && sc->sc_ravail > 0) {
c = bus_space_read_4(sc->sc_iot, sc->sc_ioh, XLCOM_RX_FIFO);
cn_check_magic(sc->sc_tty->t_dev, c, xlcom_cnm_state);
c |= ((stat & STAT_PARITY_ERR) != 0 ? XLCOM_CHAR_PE : 0) |
((stat & STAT_FRAME_ERR) != 0 ? XLCOM_CHAR_FE : 0);
sc->sc_rbuf[sc->sc_rput] = c;
sc->sc_ravail--;
next(sc->sc_rput);
stat = bus_space_read_4(sc->sc_iot, sc->sc_ioh, XLCOM_STAT);
}
if (n != sc->sc_ravail)
softint_schedule(sc->sc_rx_soft);
}
static int
xlcom_intr(void *arg)
{
struct xlcom_softc *sc = arg;
uint32_t stat;
stat = bus_space_read_4(sc->sc_iot, sc->sc_ioh, XLCOM_STAT);
if (stat & STAT_RX_DATA)
xlcom_recv_chunk(sc);
if (stat & STAT_TX_EMPTY)
xlcom_send_chunk(sc);
return (0);
}
static int
xlcom_open(dev_t dev, int flags, int mode, struct lwp *l)
{
struct xlcom_softc *sc;
struct tty *tp;
int error, s;
sc = device_lookup_private(&xlcom_cd, minor(dev));
if (sc == NULL)
return (ENXIO);
tp = sc->sc_tty;
s = spltty();
if (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN,
tp) != 0) {
error = EBUSY;
goto fail;
}
if (!(tp->t_state & TS_ISOPEN) && tp->t_wopen == 0) {
tp->t_dev = dev;
tp->t_ispeed = tp->t_ospeed = B38400;
tp->t_cflag = CLOCAL | CREAD | CS8;
tp->t_iflag = TTYDEF_IFLAG;
tp->t_oflag = TTYDEF_OFLAG;
tp->t_lflag = TTYDEF_LFLAG;
ttychars(tp);
ttsetwater(tp);
bus_space_write_4(sc->sc_iot, sc->sc_ioh, XLCOM_CNTL,
CNTL_INTR_EN);
}
error = ttyopen(tp, DIALOUT(dev), (flags & O_NONBLOCK));
if (error)
goto fail;
error = (tp->t_linesw->l_open)(dev, tp);
if (error)
goto fail;
splx(s);
return (0);
fail:
splx(s);
return (error);
}
static int
xlcom_read(dev_t dev, struct uio *uio, int flag)
{
struct xlcom_softc *sc;
struct tty *tp;
sc = device_lookup_private(&xlcom_cd, minor(dev));
if (sc == NULL)
return (ENXIO);
tp = sc->sc_tty;
return (tp->t_linesw->l_read)(tp, uio, flag);
}
static int
xlcom_write(dev_t dev, struct uio *uio, int flag)
{
struct xlcom_softc *sc;
struct tty *tp;
sc = device_lookup_private(&xlcom_cd, minor(dev));
if (sc == NULL)
return (ENXIO);
tp = sc->sc_tty;
return (tp->t_linesw->l_write)(tp, uio, flag);
}
static int
xlcom_poll(dev_t dev, int events, struct lwp *l)
{
struct xlcom_softc *sc;
struct tty *tp;
sc = device_lookup_private(&xlcom_cd, minor(dev));
if (sc == NULL)
return (ENXIO);
tp = sc->sc_tty;
return (tp->t_linesw->l_poll)(tp, events, l);
}
static struct tty *
xlcom_tty(dev_t dev)
{
struct xlcom_softc *sc;
struct tty *tp;
sc = device_lookup_private(&xlcom_cd, minor(dev));
if (sc == NULL)
return (NULL);
tp = sc->sc_tty;
return (tp);
}
static int
xlcom_ioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
struct xlcom_softc *sc;
struct tty *tp;
int error;
sc = device_lookup_private(&xlcom_cd, minor(dev));
if (sc == NULL)
return (ENXIO);
tp = sc->sc_tty;
error = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, l);
if (error != EPASSTHROUGH)
return (error);
error = ttioctl(tp, cmd, data, flag, l);
if (error != EPASSTHROUGH)
return (error);
return (error);
}
static int
xlcom_close(dev_t dev, int flag, int mode, struct lwp *l)
{
struct xlcom_softc *sc;
struct tty *tp;
sc = device_lookup_private(&xlcom_cd, minor(dev));
if (sc == NULL)
return (ENXIO);
tp = sc->sc_tty;
(tp->t_linesw->l_close)(tp, flag);
ttyclose(tp);
if (!(tp->t_state & TS_ISOPEN) && tp->t_wopen == 0) {
}
return (0);
}
static void
xlcom_stop(struct tty *tp, int flag)
{
struct xlcom_softc *sc;
int s;
sc = device_lookup_private(&xlcom_cd, UNIT(tp->t_dev));
if (sc == NULL)
return ;
s = splserial();
if (tp->t_state & TS_BUSY) {
if (! (tp->t_state & TS_TTSTOP))
tp->t_state |= TS_FLUSH;
}
splx(s);
}
static int
xlcom_param(struct tty *tp, struct termios *t)
{
t->c_cflag &= ~HUPCL;
if (tp->t_ospeed == t->c_ospeed &&
tp->t_ispeed == t->c_ispeed &&
tp->t_cflag == t->c_cflag)
return (0);
return (EINVAL);
}
static void
xlcom_start(struct tty *tp)
{
struct xlcom_softc *sc;
int s1, s2;
sc = device_lookup_private(&xlcom_cd, UNIT(tp->t_dev));
if (sc == NULL)
return ;
s1 = spltty();
if (tp->t_state & (TS_BUSY | TS_TIMEOUT | TS_TTSTOP)) {
splx(s1);
return ;
}
if (!ttypull(tp)) {
splx(s1);
return;
}
tp->t_state |= TS_BUSY;
splx(s1);
s2 = splserial();
sc->sc_tba = tp->t_outq.c_cf;
sc->sc_tbc = ndqb(&tp->t_outq, 0);
xlcom_send_chunk(sc);
splx(s2);
}
static void
xlcom_reset(bus_space_tag_t iot, bus_space_handle_t ioh)
{
while (! (bus_space_read_4(iot, ioh, XLCOM_STAT) & STAT_TX_EMPTY))
;
bus_space_write_4(iot, ioh, XLCOM_CNTL, CNTL_RX_CLEAR | CNTL_TX_CLEAR);
}
static int
xlcom_busy_getc(bus_space_tag_t t, bus_space_handle_t h)
{
while (! (bus_space_read_4(t, h, XLCOM_STAT) & STAT_RX_DATA))
;
return (bus_space_read_4(t, h, XLCOM_RX_FIFO));
}
static void
xlcom_busy_putc(bus_space_tag_t t, bus_space_handle_t h, int c)
{
while (bus_space_read_4(t, h, XLCOM_STAT) & STAT_TX_FULL)
;
bus_space_write_4(t, h, XLCOM_TX_FIFO, c);
}
void
nullcnprobe(struct consdev *cn)
{
}
void
xlcom_cninit(struct consdev *cn)
{
if (bus_space_map(consdev_iot, CONS_ADDR, XLCOM_SIZE, 0, &consdev_ioh))
panic("xlcom_cninit: could not map consdev_ioh");
xlcom_reset(consdev_iot, consdev_ioh);
}
static int
xlcom_cngetc(dev_t dev)
{
return (xlcom_busy_getc(consdev_iot, consdev_ioh));
}
static void
xlcom_cnputc(dev_t dev, int c)
{
xlcom_busy_putc(consdev_iot, consdev_ioh, c);
}
#if defined(KGDB)
static int
xlcom_kgdb_getc(void *arg)
{
return (xlcom_busy_getc(kgdb_iot, kgdb_ioh));
}
static void
xlcom_kgdb_putc(void *arg, int c)
{
xlcom_busy_putc(kgdb_iot, kgdb_ioh, c);
}
void
xlcom_kgdbinit(void)
{
if (bus_space_map(kgdb_iot, KGDB_ADDR, XLCOM_SIZE, 0, &kgdb_ioh))
panic("xlcom_kgdbinit: could not map kgdb_ioh");
xlcom_reset(kgdb_iot, kgdb_ioh);
kgdb_attach(xlcom_kgdb_getc, xlcom_kgdb_putc, NULL);
kgdb_dev = 123;
}
#endif