diff options
Diffstat (limited to 'sys/dev/xen/console/console.c')
-rw-r--r-- | sys/dev/xen/console/console.c | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/sys/dev/xen/console/console.c b/sys/dev/xen/console/console.c new file mode 100644 index 0000000..dc9fe6f --- /dev/null +++ b/sys/dev/xen/console/console.c @@ -0,0 +1,564 @@ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/module.h> +#include <sys/systm.h> +#include <sys/consio.h> +#include <sys/proc.h> +#include <sys/uio.h> +#include <sys/tty.h> +#include <sys/systm.h> +#include <sys/taskqueue.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <machine/stdarg.h> +#include <machine/xen/xen-os.h> +#include <machine/xen/hypervisor.h> +#include <machine/xen/xen_intr.h> +#include <sys/cons.h> +#include <sys/priv.h> +#include <sys/proc.h> + +#include <dev/xen/console/xencons_ring.h> +#include <xen/interface/io/console.h> + + +#include "opt_ddb.h" +#ifdef DDB +#include <ddb/ddb.h> +#endif + +static char driver_name[] = "xc"; +devclass_t xc_devclass; /* do not make static */ +static void xcstart (struct tty *); +static int xcparam (struct tty *, struct termios *); +static void xcstop (struct tty *, int); +static void xc_timeout(void *); +static void __xencons_tx_flush(void); +static boolean_t xcons_putc(int c); + +/* switch console so that shutdown can occur gracefully */ +static void xc_shutdown(void *arg, int howto); +static int xc_mute; + +static void xcons_force_flush(void); +static void xencons_priv_interrupt(void *); + +static cn_probe_t xccnprobe; +static cn_init_t xccninit; +static cn_getc_t xccngetc; +static cn_putc_t xccnputc; +static cn_putc_t xccnputc_dom0; +static cn_checkc_t xccncheckc; + +#define XC_POLLTIME (hz/10) + +CONS_DRIVER(xc, xccnprobe, xccninit, NULL, xccngetc, + xccncheckc, xccnputc, NULL); + +static int xen_console_up; +static boolean_t xc_start_needed; +static struct callout xc_callout; +struct mtx cn_mtx; + +#define RBUF_SIZE 1024 +#define RBUF_MASK(_i) ((_i)&(RBUF_SIZE-1)) +#define WBUF_SIZE 4096 +#define WBUF_MASK(_i) ((_i)&(WBUF_SIZE-1)) +static char wbuf[WBUF_SIZE]; +static char rbuf[RBUF_SIZE]; +static int rc, rp; +static unsigned int cnsl_evt_reg; +static unsigned int wc, wp; /* write_cons, write_prod */ + +#define CDEV_MAJOR 12 +#define XCUNIT(x) (minor(x)) +#define ISTTYOPEN(tp) ((tp) && ((tp)->t_state & TS_ISOPEN)) +#define CN_LOCK_INIT(x, _name) \ + mtx_init(&x, _name, NULL, MTX_SPIN|MTX_RECURSE) + +#define CN_LOCK(l) \ + do { \ + if (panicstr == NULL) \ + mtx_lock_spin(&(l)); \ + } while (0) +#define CN_UNLOCK(l) \ + do { \ + if (panicstr == NULL) \ + mtx_unlock_spin(&(l)); \ + } while (0) +#define CN_LOCK_ASSERT(x) mtx_assert(&x, MA_OWNED) +#define CN_LOCK_DESTROY(x) mtx_destroy(&x) + + +static struct tty *xccons; + +struct xc_softc { + int xc_unit; + struct cdev *xc_dev; +}; + + +static d_open_t xcopen; +static d_close_t xcclose; +static d_ioctl_t xcioctl; + +static struct cdevsw xc_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_TTY | D_NEEDGIANT, + .d_name = driver_name, + .d_open = xcopen, + .d_close = xcclose, + .d_read = ttyread, + .d_write = ttywrite, + .d_ioctl = xcioctl, + .d_poll = ttypoll, + .d_kqfilter = ttykqfilter, +}; + +static void +xccnprobe(struct consdev *cp) +{ + cp->cn_pri = CN_REMOTE; + cp->cn_tp = xccons; + sprintf(cp->cn_name, "%s0", driver_name); +} + + +static void +xccninit(struct consdev *cp) +{ + CN_LOCK_INIT(cn_mtx,"XCONS LOCK"); + +} +int +xccngetc(struct consdev *dev) +{ + int c; + if (xc_mute) + return 0; + do { + if ((c = xccncheckc(dev)) == -1) { + /* polling without sleeping in Xen doesn't work well. + * Sleeping gives other things like clock a chance to + * run + */ + tsleep(&cn_mtx, PWAIT | PCATCH, "console sleep", + XC_POLLTIME); + } + } while(c == -1); + return c; +} + +int +xccncheckc(struct consdev *dev) +{ + int ret = (xc_mute ? 0 : -1); + if (xencons_has_input()) + xencons_handle_input(NULL); + + CN_LOCK(cn_mtx); + if ((rp - rc)) { + /* we need to return only one char */ + ret = (int)rbuf[RBUF_MASK(rc)]; + rc++; + } + CN_UNLOCK(cn_mtx); + return(ret); +} + +static void +xccnputc(struct consdev *dev, int c) +{ + xcons_putc(c); +} + +static void +xccnputc_dom0(struct consdev *dev, int c) +{ + HYPERVISOR_console_io(CONSOLEIO_write, 1, (char *)&c); +} + +extern int db_active; +static boolean_t +xcons_putc(int c) +{ + int force_flush = xc_mute || +#ifdef DDB + db_active || +#endif + panicstr; /* we're not gonna recover, so force + * flush + */ + + if ((wp-wc) < (WBUF_SIZE-1)) { + if ((wbuf[WBUF_MASK(wp++)] = c) == '\n') { + wbuf[WBUF_MASK(wp++)] = '\r'; +#ifdef notyet + if (force_flush) + xcons_force_flush(); +#endif + } + } else if (force_flush) { +#ifdef notyet + xcons_force_flush(); +#endif + } + if (cnsl_evt_reg) + __xencons_tx_flush(); + + /* inform start path that we're pretty full */ + return ((wp - wc) >= WBUF_SIZE - 100) ? TRUE : FALSE; +} + +static void +xc_identify(driver_t *driver, device_t parent) +{ + device_t child; + child = BUS_ADD_CHILD(parent, 0, driver_name, 0); + device_set_driver(child, driver); + device_set_desc(child, "Xen Console"); +} + +static int +xc_probe(device_t dev) +{ + struct xc_softc *sc = (struct xc_softc *)device_get_softc(dev); + + sc->xc_unit = device_get_unit(dev); + return (0); +} + +static int +xc_attach(device_t dev) +{ + struct xc_softc *sc = (struct xc_softc *)device_get_softc(dev); + + + if (xen_start_info->flags & SIF_INITDOMAIN) { + xc_consdev.cn_putc = xccnputc_dom0; + } + + sc->xc_dev = make_dev(&xc_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "xc%r", 0); + xccons = ttyalloc(); + + sc->xc_dev->si_drv1 = (void *)sc; + sc->xc_dev->si_tty = xccons; + + xccons->t_oproc = xcstart; + xccons->t_param = xcparam; + xccons->t_stop = xcstop; + xccons->t_dev = sc->xc_dev; + + callout_init(&xc_callout, 0); + + xencons_ring_init(); + + cnsl_evt_reg = 1; + callout_reset(&xc_callout, XC_POLLTIME, xc_timeout, xccons); + + if (xen_start_info->flags & SIF_INITDOMAIN) { + PANIC_IF(bind_virq_to_irqhandler( + VIRQ_CONSOLE, + 0, + "console", + NULL, + xencons_priv_interrupt, + INTR_TYPE_TTY) < 0); + + } + + + /* register handler to flush console on shutdown */ + if ((EVENTHANDLER_REGISTER(shutdown_post_sync, xc_shutdown, + NULL, SHUTDOWN_PRI_DEFAULT)) == NULL) + printf("xencons: shutdown event registration failed!\n"); + + TRACE_EXIT; + return (0); +} + +/* + * return 0 for all console input, force flush all output. + */ +static void +xc_shutdown(void *arg, int howto) +{ + xc_mute = 1; + xcons_force_flush(); +} + +void +xencons_rx(char *buf, unsigned len) +{ + int i; + struct tty *tp = xccons; + + for (i = 0; i < len; i++) { + if (xen_console_up) + (*linesw[tp->t_line]->l_rint)(buf[i], tp); + else + rbuf[RBUF_MASK(rp++)] = buf[i]; + } +} + +static void +__xencons_tx_flush(void) +{ + int sz, work_done = 0; + + CN_LOCK(cn_mtx); + while (wc != wp) { + int sent; + sz = wp - wc; + if (sz > (WBUF_SIZE - WBUF_MASK(wc))) + sz = WBUF_SIZE - WBUF_MASK(wc); + if (xen_start_info->flags & SIF_INITDOMAIN) { + HYPERVISOR_console_io(CONSOLEIO_write, sz, &wbuf[WBUF_MASK(wc)]); + wc += sz; + } else { + sent = xencons_ring_send(&wbuf[WBUF_MASK(wc)], sz); + if (sent == 0) + break; + wc += sent; + } + work_done = 1; + } + CN_UNLOCK(cn_mtx); + + /* + * ttwakeup calls routines using blocking locks + * + */ + if (work_done && xen_console_up && curthread->td_critnest == 0) + ttwakeup(xccons); +} + +void +xencons_tx(void) +{ + __xencons_tx_flush(); +} + +static void +xencons_priv_interrupt(void *arg) +{ + + static char rbuf[16]; + int l; + + while ((l = HYPERVISOR_console_io(CONSOLEIO_read, 16, rbuf)) > 0) + xencons_rx(rbuf, l); + + xencons_tx(); +} + +int +xcopen(struct cdev *dev, int flag, int mode, struct thread *td) +{ + struct xc_softc *sc; + int unit = XCUNIT(dev); + struct tty *tp; + int s, error; + + sc = (struct xc_softc *)device_get_softc( + devclass_get_device(xc_devclass, unit)); + if (sc == NULL) + return (ENXIO); + + TRACE_ENTER; + tp = dev->si_tty; + s = spltty(); + if (!ISTTYOPEN(tp)) { + tp->t_state |= TS_CARR_ON; + ttychars(tp); + tp->t_iflag = TTYDEF_IFLAG; + tp->t_oflag = TTYDEF_OFLAG; + tp->t_cflag = TTYDEF_CFLAG|CLOCAL; + tp->t_lflag = TTYDEF_LFLAG; + tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED; + xcparam(tp, &tp->t_termios); + ttsetwater(tp); + } else if (tp->t_state & TS_XCLUDE && priv_check(td, PRIV_ROOT)) { + splx(s); + return (EBUSY); + } + splx(s); + + xen_console_up = 1; + + error = (*linesw[tp->t_line]->l_open)(dev, tp); + TRACE_EXIT; + return error; +} + +int +xcclose(struct cdev *dev, int flag, int mode, struct thread *td) +{ + struct tty *tp = dev->si_tty; + + if (tp == NULL) + return (0); + xen_console_up = 0; + + spltty(); + (*linesw[tp->t_line]->l_close)(tp, flag); + tty_close(tp); + spl0(); + return (0); +} + + +int +xcioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) +{ + struct tty *tp = dev->si_tty; + int error; + + error = (*linesw[tp->t_line]->l_ioctl)(tp, cmd, data, flag, td); + if (error != ENOIOCTL) + return (error); + + error = ttioctl(tp, cmd, data, flag); + + if (error != ENOIOCTL) + return (error); + + return (ENOTTY); +} + +static inline int +__xencons_put_char(int ch) +{ + char _ch = (char)ch; + if ((wp - wc) == WBUF_SIZE) + return 0; + wbuf[WBUF_MASK(wp++)] = _ch; + return 1; +} + + +static void +xcstart(struct tty *tp) +{ + boolean_t cons_full = FALSE; + + CN_LOCK(cn_mtx); + if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { + CN_UNLOCK(cn_mtx); + + ttwwakeup(tp); + return; + } + + tp->t_state |= TS_BUSY; + CN_UNLOCK(cn_mtx); + + while (tp->t_outq.c_cc != 0 && !cons_full) + cons_full = xcons_putc(getc(&tp->t_outq)); + + /* if the console is close to full leave our state as busy */ + if (!cons_full) { + CN_LOCK(cn_mtx); + tp->t_state &= ~TS_BUSY; + CN_UNLOCK(cn_mtx); + ttwwakeup(tp); + } else { + /* let the timeout kick us in a bit */ + xc_start_needed = TRUE; + } + +} + +static void +xcstop(struct tty *tp, int flag) +{ + + if (tp->t_state & TS_BUSY) { + if ((tp->t_state & TS_TTSTOP) == 0) { + tp->t_state |= TS_FLUSH; + } + } +} + +static void +xc_timeout(void *v) +{ + struct tty *tp; + int c; + + tp = (struct tty *)v; + + while ((c = xccncheckc(NULL)) != -1) { + if (tp->t_state & TS_ISOPEN) { + (*linesw[tp->t_line]->l_rint)(c, tp); + } + } + + if (xc_start_needed) { + xc_start_needed = FALSE; + xcstart(tp); + } + + callout_reset(&xc_callout, XC_POLLTIME, xc_timeout, tp); +} + +/* + * Set line parameters. + */ +int +xcparam(struct tty *tp, struct termios *t) +{ + tp->t_ispeed = t->c_ispeed; + tp->t_ospeed = t->c_ospeed; + tp->t_cflag = t->c_cflag; + return (0); +} + + +static device_method_t xc_methods[] = { + DEVMETHOD(device_identify, xc_identify), + DEVMETHOD(device_probe, xc_probe), + DEVMETHOD(device_attach, xc_attach), + {0, 0} +}; + +static driver_t xc_driver = { + driver_name, + xc_methods, + sizeof(struct xc_softc), +}; + +/*** Forcibly flush console data before dying. ***/ +void +xcons_force_flush(void) +{ + int sz; + + if (xen_start_info->flags & SIF_INITDOMAIN) + return; + + /* Spin until console data is flushed through to the domain controller. */ + while (wc != wp) { + int sent = 0; + if ((sz = wp - wc) == 0) + continue; + + sent = xencons_ring_send(&wbuf[WBUF_MASK(wc)], sz); + if (sent > 0) + wc += sent; + } +} + +DRIVER_MODULE(xc, nexus, xc_driver, xc_devclass, 0, 0); +/* + * Local variables: + * mode: C + * c-set-style: "BSD" + * c-basic-offset: 8 + * tab-width: 4 + * indent-tabs-mode: t + * End: + */ |