freebsd-dev/sys/dev/xen/console/console.c
Ed Schouten 1781f8bd6d Integrate the Xen console driver.
I initially didn't want to integrate the Xen console driver, because it
did not receive any testing. Kip Macy suggested that I'd better check it
in right now, because this is the easiest way for him to test it while
he is working on the Xen import.

Requested by:	kmacy
2008-08-20 09:03:03 +00:00

433 lines
8.1 KiB
C

#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 xcoutwakeup(struct tty *);
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;
static tsw_open_t xcopen;
static tsw_close_t xcclose;
static struct ttydevsw xc_ttydevsw = {
.tsw_flags = TF_NOPREFIX,
.tsw_open = xcopen,
.tsw_close = xcclose,
.tsw_outwakeup = xcoutwakeup,
};
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)
{
return (0);
}
static int
xc_attach(device_t dev)
{
if (xen_start_info->flags & SIF_INITDOMAIN) {
xc_consdev.cn_putc = xccnputc_dom0;
}
xccons = tty_alloc(&xc_ttydevsw, NULL, NULL);
tty_makedev(xccons, NULL, "xc%r", 0);
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");
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;
if (xen_console_up) {
tty_lock(tp);
for (i = 0; i < len; i++)
ttydisc_rint(tp, buf[i], 0);
ttydisc_rint_done(tp);
tty_unlock(tp);
} else {
for (i = 0; i < len; i++)
rbuf[RBUF_MASK(rp++)] = buf[i];
}
}
static void
__xencons_tx_flush(void)
{
int sz;
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;
}
}
CN_UNLOCK(cn_mtx);
}
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();
}
static int
xcopen(struct tty *tp)
{
xen_console_up = 1;
return (0);
}
static void
xcclose(struct tty *tp)
{
xen_console_up = 0;
}
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
xcoutwakeup(struct tty *tp)
{
boolean_t cons_full = FALSE;
char c;
while (ttydisc_getc(tp, &c, 1) == 1 && !cons_full)
cons_full = xcons_putc(c);
if (cons_full) {
/* let the timeout kick us in a bit */
xc_start_needed = TRUE;
}
}
static void
xc_timeout(void *v)
{
struct tty *tp;
int c;
tp = (struct tty *)v;
tty_lock(tp);
while ((c = xccncheckc(NULL)) != -1)
ttydisc_rint(tp, c, 0);
tty_unlock(tp);
if (xc_start_needed) {
xc_start_needed = FALSE;
xcoutwakeup(tp);
}
callout_reset(&xc_callout, XC_POLLTIME, xc_timeout, tp);
}
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,
0,
};
/*** 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:
*/