freebsd-nq/sys/dev/xen/console/console.c
Roger Pau Monné aa64d12bcd xen: introduce xenpv bus
Create a dummy bus so top level Xen devices can attach to it (instead
of attaching directly to the nexus). This allows to have all the Xen
related devices grouped under a single bus.

Sponsored by: Citrix Systems R&D
Approved by: gibbs

x86/xen/xenpv.c:
 - Attach the xenpv bus when running as a Xen guest.
 - Attach the ISA bus if needed, in order to attach syscons.

conf/files.amd6:
conf/files.i386:
 - Include the xenpv.c file in the build of i386/amd64 kernels using
   XENHVM.

dev/xen/console/console.c:
dev/xen/timer/timer.c:
xen/xenstore/xenstore.c:
 - Attach to the xenpv bus instead of the Nexus.

dev/xen/xenpci/xenpci.c:
 - Xen specific devices on PVHVM guests are no longer attached to the
   xenpci device, they are instead attached to the xenpv bus, remove
   the now unused methods.
2014-06-16 08:44:33 +00:00

513 lines
9.3 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/priv.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 <xen/xen-os.h>
#include <xen/hypervisor.h>
#include <xen/xen_intr.h>
#include <sys/cons.h>
#include <sys/kdb.h>
#include <sys/proc.h>
#include <dev/xen/console/xencons_ring.h>
#include <xen/interface/io/console.h>
#include "opt_ddb.h"
#include "opt_printf.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 xc_cnprobe;
static cn_init_t xc_cninit;
static cn_term_t xc_cnterm;
static cn_getc_t xc_cngetc;
static cn_putc_t xc_cnputc;
static cn_grab_t xc_cngrab;
static cn_ungrab_t xc_cnungrab;
#define XC_POLLTIME (hz/10)
CONSOLE_DRIVER(xc);
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;
bool cnsl_evt_reg;
static unsigned int wc, wp; /* write_cons, write_prod */
xen_intr_handle_t xen_intr_handle;
device_t xencons_dev;
/* Virtual address of the shared console page */
char *console_page;
#ifdef KDB
static int xc_altbrk;
#endif
#define CDEV_MAJOR 12
#define XCUNIT(x) (dev2unit(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,
};
/*----------------------------- Debug function -------------------------------*/
struct putchar_arg {
char *buf;
size_t size;
size_t n_next;
};
static void
putchar(int c, void *arg)
{
struct putchar_arg *pca;
pca = (struct putchar_arg *)arg;
if (pca->buf == NULL) {
/*
* We have no buffer, output directly to the
* console char by char.
*/
HYPERVISOR_console_write((char *)&c, 1);
} else {
pca->buf[pca->n_next++] = c;
if ((pca->size == pca->n_next) || (c = '\0')) {
/* Flush the buffer */
HYPERVISOR_console_write(pca->buf, pca->n_next);
pca->n_next = 0;
}
}
}
void
xc_printf(const char *fmt, ...)
{
va_list ap;
struct putchar_arg pca;
#ifdef PRINTF_BUFR_SIZE
char buf[PRINTF_BUFR_SIZE];
pca.buf = buf;
pca.size = sizeof(buf);
pca.n_next = 0;
#else
pca.buf = NULL;
pca.size = 0;
#endif
KASSERT((xen_domain()), ("call to xc_printf from non Xen guest"));
va_start(ap, fmt);
kvprintf(fmt, putchar, &pca, 10, ap);
va_end(ap);
#ifdef PRINTF_BUFR_SIZE
if (pca.n_next != 0)
HYPERVISOR_console_write(buf, pca.n_next);
#endif
}
static void
xc_cnprobe(struct consdev *cp)
{
if (!xen_pv_domain())
return;
cp->cn_pri = CN_REMOTE;
sprintf(cp->cn_name, "%s0", driver_name);
}
static void
xc_cninit(struct consdev *cp)
{
CN_LOCK_INIT(cn_mtx,"XCONS LOCK");
}
static void
xc_cnterm(struct consdev *cp)
{
}
static void
xc_cngrab(struct consdev *cp)
{
}
static void
xc_cnungrab(struct consdev *cp)
{
}
static int
xc_cngetc(struct consdev *dev)
{
int ret;
if (xencons_has_input())
xencons_handle_input(NULL);
CN_LOCK(cn_mtx);
if ((rp - rc) && !xc_mute) {
/* we need to return only one char */
ret = (int)rbuf[RBUF_MASK(rc)];
rc++;
} else
ret = -1;
CN_UNLOCK(cn_mtx);
return(ret);
}
static void
xc_cnputc_domu(struct consdev *dev, int c)
{
xcons_putc(c);
}
static void
xc_cnputc_dom0(struct consdev *dev, int c)
{
HYPERVISOR_console_io(CONSOLEIO_write, 1, (char *)&c);
}
static void
xc_cnputc(struct consdev *dev, int c)
{
if (xen_initial_domain())
xc_cnputc_dom0(dev, c);
else
xc_cnputc_domu(dev, c);
}
static boolean_t
xcons_putc(int c)
{
int force_flush = xc_mute ||
#ifdef DDB
kdb_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
}
__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;
if (!xen_pv_domain())
return;
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 (BUS_PROBE_NOWILDCARD);
}
static int
xc_attach(device_t dev)
{
int error;
xencons_dev = dev;
xccons = tty_alloc(&xc_ttydevsw, NULL);
tty_makedev(xccons, NULL, "xc%r", 0);
callout_init(&xc_callout, 0);
xencons_ring_init();
cnsl_evt_reg = true;
callout_reset(&xc_callout, XC_POLLTIME, xc_timeout, xccons);
if (xen_initial_domain()) {
error = xen_intr_bind_virq(dev, VIRQ_CONSOLE, 0, NULL,
xencons_priv_interrupt, NULL,
INTR_TYPE_TTY, &xen_intr_handle);
KASSERT(error >= 0, ("can't register console interrupt"));
}
/* 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
#ifdef DDB
&& !kdb_active
#endif
) {
tty_lock(tp);
for (i = 0; i < len; i++) {
#ifdef KDB
kdb_alt_break(buf[i], &xc_altbrk);
#endif
ttydisc_rint(tp, buf[i], 0);
}
ttydisc_rint_done(tp);
tty_unlock(tp);
} else {
CN_LOCK(cn_mtx);
for (i = 0; i < len; i++)
rbuf[RBUF_MASK(rp++)] = buf[i];
CN_UNLOCK(cn_mtx);
}
}
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_initial_domain()) {
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;
}
#if 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;
}
#endif
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 = xc_cngetc(NULL)) != -1)
ttydisc_rint(tp, c, 0);
if (xc_start_needed) {
xc_start_needed = FALSE;
xcoutwakeup(tp);
}
tty_unlock(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),
DEVMETHOD_END
};
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_initial_domain())
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, xenpv, xc_driver, xc_devclass, 0, 0);