summaryrefslogtreecommitdiffstats
path: root/sys/dev/xen/console/console.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/xen/console/console.c')
-rw-r--r--sys/dev/xen/console/console.c564
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:
+ */
OpenPOWER on IntegriCloud