#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xenfb.h" // FIXME defend against malicious frontend? struct xenfb_device { const char *devicetype; char nodename[64]; /* backend xenstore dir */ char otherend[64]; /* frontend xenstore dir */ int otherend_id; /* frontend domid */ enum xenbus_state state; /* backend state */ void *page; /* shared page */ evtchn_port_t port; struct xenfb_private *xenfb; }; struct xenfb_private { struct xenfb pub; int evt_xch; /* event channel driver handle */ int xc; /* hypervisor interface handle */ struct xs_handle *xsh; /* xs daemon handle */ struct xenfb_device fb, kbd; size_t fb_len; /* size of framebuffer */ char protocol[64]; /* frontend protocol */ }; static void xenfb_detach_dom(struct xenfb_private *); static char *xenfb_path_in_dom(struct xs_handle *xsh, char *buf, size_t size, unsigned domid, const char *fmt, ...) { va_list ap; char *domp = xs_get_domain_path(xsh, domid); int n; if (domp == NULL) return NULL; n = snprintf(buf, size, "%s/", domp); free(domp); if (n >= size) return NULL; va_start(ap, fmt); n += vsnprintf(buf + n, size - n, fmt, ap); va_end(ap); if (n >= size) return NULL; return buf; } static int xenfb_xs_scanf1(struct xs_handle *xsh, const char *dir, const char *node, const char *fmt, void *dest) { char buf[1024]; char *p; int ret; if (snprintf(buf, sizeof(buf), "%s/%s", dir, node) >= sizeof(buf)) { errno = ENOENT; return -1; } p = xs_read(xsh, XBT_NULL, buf, NULL); if (!p) { errno = ENOENT; return -1; } ret = sscanf(p, fmt, dest); free(p); if (ret != 1) { errno = EDOM; return -1; } return ret; } static int xenfb_xs_printf(struct xs_handle *xsh, const char *dir, const char *node, char *fmt, ...) { va_list ap; char key[1024]; char val[1024]; int n; if (snprintf(key, sizeof(key), "%s/%s", dir, node) >= sizeof(key)) { errno = ENOENT; return -1; } va_start(ap, fmt); n = vsnprintf(val, sizeof(val), fmt, ap); va_end(ap); if (n >= sizeof(val)) { errno = ENOSPC; /* close enough */ return -1; } if (!xs_write(xsh, XBT_NULL, key, val, n)) return -1; return 0; } static void xenfb_device_init(struct xenfb_device *dev, const char *type, struct xenfb_private *xenfb) { dev->devicetype = type; dev->otherend_id = -1; dev->port = -1; dev->xenfb = xenfb; } int xenfb_device_set_domain(struct xenfb_device *dev, int domid) { struct xenfb_private *xenfb = dev->xenfb; dev->otherend_id = domid; if (!xenfb_path_in_dom(xenfb->xsh, dev->otherend, sizeof(dev->otherend), domid, "device/%s/0", dev->devicetype)) { errno = ENOENT; return -1; } if (!xenfb_path_in_dom(xenfb->xsh, dev->nodename, sizeof(dev->nodename), 0, "backend/%s/%d/0", dev->devicetype, domid)) { errno = ENOENT; return -1; } return 0; } struct xenfb *xenfb_new(void) { struct xenfb_private *xenfb = malloc(sizeof(*xenfb)); int serrno; if (xenfb == NULL) return NULL; memset(xenfb, 0, sizeof(*xenfb)); xenfb->evt_xch = xenfb->xc = -1; xenfb_device_init(&xenfb->fb, "vfb", xenfb); xenfb_device_init(&xenfb->kbd, "vkbd", xenfb); xenfb->evt_xch = xc_evtchn_open(); if (xenfb->evt_xch == -1) goto fail; xenfb->xc = xc_interface_open(); if (xenfb->xc == -1) goto fail; xenfb->xsh = xs_daemon_open(); if (!xenfb->xsh) goto fail; return &xenfb->pub; fail: serrno = errno; xenfb_delete(&xenfb->pub); errno = serrno; return NULL; } /* Remove the backend area in xenbus since the framebuffer really is going away. */ void xenfb_teardown(struct xenfb *xenfb_pub) { struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; xs_rm(xenfb->xsh, XBT_NULL, xenfb->fb.nodename); xs_rm(xenfb->xsh, XBT_NULL, xenfb->kbd.nodename); } void xenfb_delete(struct xenfb *xenfb_pub) { struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; xenfb_detach_dom(xenfb); if (xenfb->xc >= 0) xc_interface_close(xenfb->xc); if (xenfb->evt_xch >= 0) xc_evtchn_close(xenfb->evt_xch); if (xenfb->xsh) xs_daemon_close(xenfb->xsh); free(xenfb); } static enum xenbus_state xenfb_read_state(struct xs_handle *xsh, const char *dir) { int ret, state; ret = xenfb_xs_scanf1(xsh, dir, "state", "%d", &state); if (ret < 0) return XenbusStateUnknown; if ((unsigned)state > XenbusStateClosed) state = XenbusStateUnknown; return state; } static int xenfb_switch_state(struct xenfb_device *dev, enum xenbus_state state) { struct xs_handle *xsh = dev->xenfb->xsh; if (xenfb_xs_printf(xsh, dev->nodename, "state", "%d", state) < 0) return -1; dev->state = state; return 0; } static int xenfb_wait_for_state(struct xs_handle *xsh, const char *dir, unsigned awaited) { unsigned state, dummy; char **vec; awaited |= 1 << XenbusStateUnknown; for (;;) { state = xenfb_read_state(xsh, dir); if ((1 << state) & awaited) return state; vec = xs_read_watch(xsh, &dummy); if (!vec) return -1; free(vec); } } static int xenfb_wait_for_backend_creation(struct xenfb_device *dev) { struct xs_handle *xsh = dev->xenfb->xsh; int state; if (!xs_watch(xsh, dev->nodename, "")) return -1; state = xenfb_wait_for_state(xsh, dev->nodename, (1 << XenbusStateInitialising) | (1 << XenbusStateClosed) #if 1 /* TODO fudging state to permit restarting; to be removed */ | (1 << XenbusStateInitWait) | (1 << XenbusStateConnected) | (1 << XenbusStateClosing) #endif ); xs_unwatch(xsh, dev->nodename, ""); switch (state) { #if 1 case XenbusStateInitWait: case XenbusStateConnected: printf("Fudging state to %d\n", XenbusStateInitialising); /* FIXME */ #endif case XenbusStateInitialising: case XenbusStateClosing: case XenbusStateClosed: break; default: return -1; } return 0; } static int xenfb_hotplug(struct xenfb_device *dev) { if (xenfb_xs_printf(dev->xenfb->xsh, dev->nodename, "hotplug-status", "connected")) return -1; return 0; } static int xenfb_wait_for_frontend_initialised(struct xenfb_device *dev) { switch (xenfb_wait_for_state(dev->xenfb->xsh, dev->otherend, #if 1 /* TODO fudging state to permit restarting; to be removed */ (1 << XenbusStateInitialised) | (1 << XenbusStateConnected) #else 1 << XenbusStateInitialised, #endif )) { #if 1 case XenbusStateConnected: printf("Fudging state to %d\n", XenbusStateInitialised); /* FIXME */ #endif case XenbusStateInitialised: break; default: return -1; } return 0; } static void xenfb_copy_mfns(int mode, int count, unsigned long *dst, void *src) { uint32_t *src32 = src; uint64_t *src64 = src; int i; for (i = 0; i < count; i++) dst[i] = (mode == 32) ? src32[i] : src64[i]; } static int xenfb_map_fb(struct xenfb_private *xenfb, int domid) { struct xenfb_page *page = xenfb->fb.page; int n_fbmfns; int n_fbdirs; unsigned long *pgmfns = NULL; unsigned long *fbmfns = NULL; void *map, *pd; int mode, ret = -1; /* default to native */ pd = page->pd; mode = sizeof(unsigned long) * 8; if (0 == strlen(xenfb->protocol)) { /* * Undefined protocol, some guesswork needed. * * Old frontends which don't set the protocol use * one page directory only, thus pd[1] must be zero. * pd[1] of the 32bit struct layout and the lower * 32 bits of pd[0] of the 64bit struct layout have * the same location, so we can check that ... */ uint32_t *ptr32 = NULL; uint32_t *ptr64 = NULL; #if defined(__i386__) ptr32 = (void*)page->pd; ptr64 = ((void*)page->pd) + 4; #elif defined(__x86_64__) ptr32 = ((void*)page->pd) - 4; ptr64 = (void*)page->pd; #endif if (ptr32) { if (0 == ptr32[1]) { mode = 32; pd = ptr32; } else { mode = 64; pd = ptr64; } } #if defined(__x86_64__) } else if (0 == strcmp(xenfb->protocol, XEN_IO_PROTO_ABI_X86_32)) { /* 64bit dom0, 32bit domU */ mode = 32; pd = ((void*)page->pd) - 4; #elif defined(__i386__) } else if (0 == strcmp(xenfb->protocol, XEN_IO_PROTO_ABI_X86_64)) { /* 32bit dom0, 64bit domU */ mode = 64; pd = ((void*)page->pd) + 4; #endif } n_fbmfns = (xenfb->fb_len + (XC_PAGE_SIZE - 1)) / XC_PAGE_SIZE; n_fbdirs = n_fbmfns * mode / 8; n_fbdirs = (n_fbdirs + (XC_PAGE_SIZE - 1)) / XC_PAGE_SIZE; pgmfns = malloc(sizeof(unsigned long) * n_fbdirs); fbmfns = malloc(sizeof(unsigned long) * n_fbmfns); if (!pgmfns || !fbmfns) goto out; /* * Bug alert: xc_map_foreign_batch() can fail partly and * return a non-null value. This is a design flaw. When it * happens, we happily continue here, and later crash on * access. */ xenfb_copy_mfns(mode, n_fbdirs, pgmfns, pd); map = xc_map_foreign_batch(xenfb->xc, domid, PROT_READ, pgmfns, n_fbdirs); if (map == NULL) goto out; xenfb_copy_mfns(mode, n_fbmfns, fbmfns, map); munmap(map, n_fbdirs * XC_PAGE_SIZE); xenfb->pub.pixels = xc_map_foreign_batch(xenfb->xc, domid, PROT_READ | PROT_WRITE, fbmfns, n_fbmfns); if (xenfb->pub.pixels == NULL) goto out; ret = 0; /* all is fine */ out: if (pgmfns) free(pgmfns); if (fbmfns) free(fbmfns); return ret; } static int xenfb_bind(struct xenfb_device *dev) { struct xenfb_private *xenfb = dev->xenfb; unsigned long mfn; evtchn_port_t evtchn; if (xenfb_xs_scanf1(xenfb->xsh, dev->otherend, "page-ref", "%lu", &mfn) < 0) return -1; if (xenfb_xs_scanf1(xenfb->xsh, dev->otherend, "event-channel", "%u", &evtchn) < 0) return -1; dev->port = xc_evtchn_bind_interdomain(xenfb->evt_xch, dev->otherend_id, evtchn); if (dev->port == -1) return -1; dev->page = xc_map_foreign_range(xenfb->xc, dev->otherend_id, XC_PAGE_SIZE, PROT_READ | PROT_WRITE, mfn); if (dev->page == NULL) return -1; return 0; } static void xenfb_unbind(struct xenfb_device *dev) { if (dev->page) { munmap(dev->page, XC_PAGE_SIZE); dev->page = NULL; } if (dev->port >= 0) { xc_evtchn_unbind(dev->xenfb->evt_xch, dev->port); dev->port = -1; } } static int xenfb_wait_for_frontend_connected(struct xenfb_device *dev) { switch (xenfb_wait_for_state(dev->xenfb->xsh, dev->otherend, 1 << XenbusStateConnected)) { case XenbusStateConnected: break; default: return -1; } return 0; } static void xenfb_dev_fatal(struct xenfb_device *dev, int err, const char *fmt, ...) { struct xs_handle *xsh = dev->xenfb->xsh; va_list ap; char errdir[80]; char buf[1024]; int n; fprintf(stderr, "%s ", dev->nodename); /* somewhat crude */ va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if (err) fprintf(stderr, " (%s)", strerror(err)); putc('\n', stderr); if (!xenfb_path_in_dom(xsh, errdir, sizeof(errdir), 0, "error/%s", dev->nodename)) goto out; /* FIXME complain */ va_start(ap, fmt); n = snprintf(buf, sizeof(buf), "%d ", err); snprintf(buf + n, sizeof(buf) - n, fmt, ap); va_end(ap); if (xenfb_xs_printf(xsh, buf, "error", "%s", buf) < 0) goto out; /* FIXME complain */ out: xenfb_switch_state(dev, XenbusStateClosing); } int xenfb_attach_dom(struct xenfb *xenfb_pub, int domid) { struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; struct xs_handle *xsh = xenfb->xsh; int val, serrno; struct xenfb_page *fb_page; xenfb_detach_dom(xenfb); xenfb_device_set_domain(&xenfb->fb, domid); xenfb_device_set_domain(&xenfb->kbd, domid); if (xenfb_wait_for_backend_creation(&xenfb->fb) < 0) goto error; if (xenfb_wait_for_backend_creation(&xenfb->kbd) < 0) goto error; if (xenfb_xs_printf(xsh, xenfb->kbd.nodename, "feature-abs-pointer", "1")) goto error; if (xenfb_switch_state(&xenfb->fb, XenbusStateInitWait)) goto error; if (xenfb_switch_state(&xenfb->kbd, XenbusStateInitWait)) goto error; if (xenfb_hotplug(&xenfb->fb) < 0) goto error; if (xenfb_hotplug(&xenfb->kbd) < 0) goto error; if (!xs_watch(xsh, xenfb->fb.otherend, "")) goto error; if (!xs_watch(xsh, xenfb->kbd.otherend, "")) goto error; if (xenfb_wait_for_frontend_initialised(&xenfb->fb) < 0) goto error; if (xenfb_wait_for_frontend_initialised(&xenfb->kbd) < 0) goto error; if (xenfb_bind(&xenfb->fb) < 0) goto error; if (xenfb_bind(&xenfb->kbd) < 0) goto error; if (xenfb_xs_scanf1(xsh, xenfb->fb.otherend, "feature-update", "%d", &val) < 0) val = 0; if (!val) { errno = ENOTSUP; goto error; } if (xenfb_xs_scanf1(xsh, xenfb->fb.otherend, "protocol", "%63s", xenfb->protocol) < 0) xenfb->protocol[0] = '\0'; xenfb_xs_printf(xsh, xenfb->fb.nodename, "request-update", "1"); /* TODO check for permitted ranges */ fb_page = xenfb->fb.page; xenfb->pub.depth = fb_page->depth; xenfb->pub.width = fb_page->width; xenfb->pub.height = fb_page->height; /* TODO check for consistency with the above */ xenfb->fb_len = fb_page->mem_length; xenfb->pub.row_stride = fb_page->line_length; if (xenfb_map_fb(xenfb, domid) < 0) goto error; if (xenfb_switch_state(&xenfb->fb, XenbusStateConnected)) goto error; if (xenfb_switch_state(&xenfb->kbd, XenbusStateConnected)) goto error; if (xenfb_wait_for_frontend_connected(&xenfb->kbd) < 0) goto error; if (xenfb_xs_scanf1(xsh, xenfb->kbd.otherend, "request-abs-pointer", "%d", &val) < 0) val = 0; xenfb->pub.abs_pointer_wanted = val; return 0; error: serrno = errno; xenfb_detach_dom(xenfb); xenfb_dev_fatal(&xenfb->fb, serrno, "on fire"); xenfb_dev_fatal(&xenfb->kbd, serrno, "on fire"); errno = serrno; return -1; } static void xenfb_detach_dom(struct xenfb_private *xenfb) { xenfb_unbind(&xenfb->fb); xenfb_unbind(&xenfb->kbd); if (xenfb->pub.pixels) { munmap(xenfb->pub.pixels, xenfb->fb_len); xenfb->pub.pixels = NULL; } } static void xenfb_on_fb_event(struct xenfb_private *xenfb) { uint32_t prod, cons; struct xenfb_page *page = xenfb->fb.page; prod = page->out_prod; if (prod == page->out_cons) return; rmb(); /* ensure we see ring contents up to prod */ for (cons = page->out_cons; cons != prod; cons++) { union xenfb_out_event *event = &XENFB_OUT_RING_REF(page, cons); switch (event->type) { case XENFB_TYPE_UPDATE: if (xenfb->pub.update) xenfb->pub.update(&xenfb->pub, event->update.x, event->update.y, event->update.width, event->update.height); break; } } mb(); /* ensure we're done with ring contents */ page->out_cons = cons; xc_evtchn_notify(xenfb->evt_xch, xenfb->fb.port); } static void xenfb_on_kbd_event(struct xenfb_private *xenfb) { struct xenkbd_page *page = xenfb->kbd.page; /* We don't understand any keyboard events, so just ignore them. */ if (page->out_prod == page->out_cons) return; page->out_cons = page->out_prod; xc_evtchn_notify(xenfb->evt_xch, xenfb->kbd.port); } static int xenfb_on_state_change(struct xenfb_device *dev) { enum xenbus_state state; state = xenfb_read_state(dev->xenfb->xsh, dev->otherend); switch (state) { case XenbusStateUnknown: /* There was an error reading the frontend state. The domain has probably gone away; in any case, there's not much point in us continuing. */ return -1; case XenbusStateInitialising: case XenbusStateInitWait: case XenbusStateInitialised: case XenbusStateConnected: break; case XenbusStateClosing: xenfb_unbind(dev); xenfb_switch_state(dev, state); break; case XenbusStateClosed: xenfb_switch_state(dev, state); } return 0; } /* Returns 0 normally, -1 on error, or -2 if the domain went away. */ int xenfb_poll(struct xenfb *xenfb_pub, fd_set *readfds) { struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; evtchn_port_t port; unsigned dummy; char **vec; int r; if (FD_ISSET(xc_evtchn_fd(xenfb->evt_xch), readfds)) { port = xc_evtchn_pending(xenfb->evt_xch); if (port == -1) return -1; if (port == xenfb->fb.port) xenfb_on_fb_event(xenfb); else if (port == xenfb->kbd.port) xenfb_on_kbd_event(xenfb); if (xc_evtchn_unmask(xenfb->evt_xch, port) == -1) return -1; } if (FD_ISSET(xs_fileno(xenfb->xsh), readfds)) { vec = xs_read_watch(xenfb->xsh, &dummy); free(vec); r = xenfb_on_state_change(&xenfb->fb); if (r == 0) r = xenfb_on_state_change(&xenfb->kbd); if (r == -1) return -2; } return 0; } int xenfb_select_fds(struct xenfb *xenfb_pub, fd_set *readfds) { struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; int fd1 = xc_evtchn_fd(xenfb->evt_xch); int fd2 = xs_fileno(xenfb->xsh); FD_SET(fd1, readfds); FD_SET(fd2, readfds); return fd1 > fd2 ? fd1 + 1 : fd2 + 1; } static int xenfb_kbd_event(struct xenfb_private *xenfb, union xenkbd_in_event *event) { uint32_t prod; struct xenkbd_page *page = xenfb->kbd.page; if (xenfb->kbd.state != XenbusStateConnected) return 0; prod = page->in_prod; if (prod - page->in_cons == XENKBD_IN_RING_LEN) { errno = EAGAIN; return -1; } mb(); /* ensure ring space available */ XENKBD_IN_RING_REF(page, prod) = *event; wmb(); /* ensure ring contents visible */ page->in_prod = prod + 1; return xc_evtchn_notify(xenfb->evt_xch, xenfb->kbd.port); } int xenfb_send_key(struct xenfb *xenfb_pub, bool down, int keycode) { struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; union xenkbd_in_event event; memset(&event, 0, XENKBD_IN_EVENT_SIZE); event.type = XENKBD_TYPE_KEY; event.key.pressed = down ? 1 : 0; event.key.keycode = keycode; return xenfb_kbd_event(xenfb, &event); } int xenfb_send_motion(struct xenfb *xenfb_pub, int rel_x, int rel_y) { struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; union xenkbd_in_event event; memset(&event, 0, XENKBD_IN_EVENT_SIZE); event.type = XENKBD_TYPE_MOTION; event.motion.rel_x = rel_x; event.motion.rel_y = rel_y; return xenfb_kbd_event(xenfb, &event); } int xenfb_send_position(struct xenfb *xenfb_pub, int abs_x, int abs_y) { struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; union xenkbd_in_event event; memset(&event, 0, XENKBD_IN_EVENT_SIZE); event.type = XENKBD_TYPE_POS; event.pos.abs_x = abs_x; event.pos.abs_y = abs_y; return xenfb_kbd_event(xenfb, &event); }