diff mbox

Add VirtIO Frame Buffer Support

Message ID 1257199759-2941-1-git-send-email-agraf@suse.de (mailing list archive)
State New, archived
Headers show

Commit Message

Alexander Graf Nov. 2, 2009, 10:09 p.m. UTC
None
diff mbox

Patch

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 9bbb285..f9be4c2 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2069,6 +2069,21 @@  config XEN_FBDEV_FRONTEND
 	  frame buffer driver.  It communicates with a back-end
 	  in another domain.
 
+config FB_VIRTIO
+	tristate "Virtio virtual frame buffer support"
+	depends on FB && VIRTIO
+	select FB_SYS_FILLRECT
+	select FB_SYS_COPYAREA
+	select FB_SYS_IMAGEBLIT
+	select FB_SYS_FOPS
+	select FB_DEFERRED_IO
+	help
+	  This driver implements a driver for a Virtio based
+	  frame buffer device.  It communicates to something that
+	  can talk Virtio too, most probably a hypervisor.
+
+	  If unsure, say N.
+
 config FB_METRONOME
 	tristate "E-Ink Metronome/8track controller support"
 	depends on FB
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 80232e1..40802c8 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -125,6 +125,7 @@  obj-$(CONFIG_FB_XILINX)           += xilinxfb.o
 obj-$(CONFIG_FB_SH_MOBILE_LCDC)	  += sh_mobile_lcdcfb.o
 obj-$(CONFIG_FB_OMAP)             += omap/
 obj-$(CONFIG_XEN_FBDEV_FRONTEND)  += xen-fbfront.o
+obj-$(CONFIG_FB_VIRTIO)           += virtio-fb.o
 obj-$(CONFIG_FB_CARMINE)          += carminefb.o
 obj-$(CONFIG_FB_MB862XX)	  += mb862xx/
 obj-$(CONFIG_FB_MSM)              += msm/
diff --git a/drivers/video/virtio-fb.c b/drivers/video/virtio-fb.c
new file mode 100644
index 0000000..2a73950
--- /dev/null
+++ b/drivers/video/virtio-fb.c
@@ -0,0 +1,799 @@ 
+/*
+ * VirtIO PV frame buffer device
+ *
+ * Copyright (C) 2009 Alexander Graf <agraf@suse.de>
+ *
+ *  Based on linux/drivers/video/virtio-fbfront.c
+ *
+ *  This file is subject to the terms and conditions of the GNU General Public
+ *  License. See the file COPYING in the main directory of this archive for
+ *  more details.
+ */
+
+#include <linux/console.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
+#include <linux/virtio.h>
+#include <linux/virtio_ids.h>
+#include <linux/virtio_config.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+
+#define MAX_BUF			128
+
+struct virtio_fb_info {
+	struct fb_info		*fb_info;
+	unsigned char		*fb;
+	char			*inbuf;
+
+	u16			width;
+	u16			height;
+
+	int			nr_pages;
+	int			size;
+
+	struct kmem_cache	*cmd_cache;
+	struct virtqueue	*vq_in;
+	struct virtqueue	*vq_out;
+	struct virtio_device	*vdev;
+
+	void			*last_buf[MAX_BUF];
+	int			last_buf_idx;
+	spinlock_t		vfree_lock;
+	struct work_struct	vfree_work;
+	struct work_struct	refresh_work;
+};
+
+/* guest -> Host commands */
+#define VIRTIO_FB_CMD_RESIZE            0x01                                         
+#define VIRTIO_FB_CMD_FILL              0x02                                         
+#define VIRTIO_FB_CMD_BLIT              0x03                                         
+#define VIRTIO_FB_CMD_COPY              0x04                                         
+#define VIRTIO_FB_CMD_WRITE             0x05                                         
+    
+/* host -> guest commands */                                                         
+#define VIRTIO_FB_CMD_REFRESH           0x81                                         
+
+struct virtio_fb_cmd {
+	u8			cmd;
+	union {
+		struct {
+			u16	width;
+			u16	height;
+		} resize __attribute__ ((packed));
+		struct {
+			u16	x;
+			u16	y;
+			u16	width;
+			u16	height;
+		} blit __attribute__ ((packed));
+		struct {
+			u16	x1;
+			u16	y1;
+			u16	x2;
+			u16	y2;
+			u16	width;
+			u16	height;
+		} copy_area __attribute__ ((packed));
+		struct {
+			u8	rop;
+			u16	x;
+			u16	y;
+			u16	width;
+			u16	height;
+			u32	color;
+		} fill __attribute__ ((packed));
+		struct {
+			u64	offset;
+			u64	count;
+		} write __attribute__ ((packed));
+		u8		pad[23];
+	};
+
+	union {
+		/* We remember the data pointer so we we can easily free
+		   everything later by only knowing this structure */
+		char		*send_buf;
+		u64		_pad;
+	};
+} __attribute__ ((packed));
+
+enum copy_type {
+	COPY_KERNEL,
+	COPY_USER,
+	COPY_NOCOPY,
+};
+
+#define DEFAULT_WIDTH		800
+#define DEFAULT_HEIGHT		600
+#define DEFAULT_DEPTH		32
+#define DEFAULT_MEM		8
+
+#define DEFAULT_FB_LEN (DEFAULT_WIDTH * DEFAULT_HEIGHT * DEFAULT_DEPTH / 8)
+
+enum { KPARAM_MEM, KPARAM_WIDTH, KPARAM_HEIGHT, KPARAM_CNT };
+static int video[KPARAM_CNT] = { DEFAULT_MEM, DEFAULT_WIDTH, DEFAULT_HEIGHT };
+module_param_array(video, int, NULL, 0);
+MODULE_PARM_DESC(video,
+	"Video size in M,width,height in pixels (default \""
+	str(DEFAULT_MEM) ","
+	str(DEFAULT_WIDTH) ","
+	str(DEFAULT_HEIGHT) "\")");
+
+static void virtio_fb_output(struct virtqueue *vq);
+
+static void *rvmalloc(unsigned long size)
+{
+	void *mem;
+	unsigned long adr;
+
+	size = PAGE_ALIGN(size);
+	mem = vmalloc_32(size);
+	if (!mem)
+		return NULL;
+
+	memset(mem, 0, size); /* Clear the ram out, no junk to the user */
+	adr = (unsigned long) mem;
+	while (size > 0) {
+		SetPageReserved(vmalloc_to_page((void *)adr));
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	return mem;
+}
+
+/* This is videobuf_vmalloc_to_sg() from videobuf-dma-sg.c
+   I modified it to take an extra sg entry for the cmd and work with non
+   page-aligned pointers though */
+static struct scatterlist* vmalloc_to_sg(unsigned char *virt, int length,
+					 void *cmd, int cmd_len, int *sg_elem)
+{
+	struct scatterlist *sglist;
+	struct page *pg;
+	int nr_pages = (length+PAGE_SIZE-1)/PAGE_SIZE;
+	int sg_entries;
+	int i;
+
+	/* unaligned */
+	if ((ulong)virt & ~PAGE_MASK) {
+		int tmp_len = length - (PAGE_SIZE - ((ulong)virt & ~PAGE_MASK));
+		/* how long would it be without the first non-aligned chunk? */
+		nr_pages = (tmp_len+PAGE_SIZE-1)/PAGE_SIZE;
+		/* add the first chunk */
+		nr_pages++;
+	}
+
+	sg_entries = nr_pages + 1;
+
+	sglist = kcalloc(sg_entries, sizeof(struct scatterlist), GFP_KERNEL);
+	if (!sglist)
+		return NULL;
+	sg_init_table(sglist, sg_entries);
+
+	/* Put cmd element in */
+	sg_set_buf(&sglist[0], cmd, cmd_len);
+
+	/* Fill with elements for the data */
+	for (i = 1; i < sg_entries; i++) {
+		pg = vmalloc_to_page(virt);
+		if (!pg)
+			goto err;
+
+		if ((ulong)virt & ~PAGE_MASK) {
+			int tmp_off = ((ulong)virt & ~PAGE_MASK);
+
+			sg_set_page(&sglist[i], pg, PAGE_SIZE - tmp_off, tmp_off);
+			virt = (char*)((ulong)virt & PAGE_MASK);
+		} else {
+			sg_set_page(&sglist[i], pg, PAGE_SIZE, 0);
+		}
+		virt += PAGE_SIZE;
+	}
+
+	*sg_elem = sg_entries;
+	return sglist;
+
+ err:
+	kfree(sglist);
+	return NULL;
+}
+
+
+static void _virtio_fb_send(struct virtio_fb_info *info,
+			    struct virtio_fb_cmd *cmd,
+			    char *buf, int len, enum copy_type copy)
+{
+	char *send_buf = NULL;
+	char *sg_buf = NULL;
+	struct virtio_fb_cmd *send_cmd;
+	struct scatterlist *sg;
+	int len_cmd = sizeof(struct virtio_fb_cmd);
+	int r, sg_elem;
+
+	send_cmd = kmem_cache_alloc(info->cmd_cache, GFP_KERNEL);
+	if (!send_cmd) {
+		printk(KERN_ERR "virtio-fb: couldn't allocate cmd\n");
+		return;
+	}
+
+	memcpy(send_cmd, cmd, len_cmd);
+
+	if (len) {
+		sg_buf = send_buf = vmalloc(len);
+		if (!send_buf) {
+			printk(KERN_ERR "virtio-fb: couldn't allocate %d b\n",
+					len);
+			return;
+		}
+
+		switch (copy) {
+		case COPY_KERNEL:
+			memcpy(send_buf, buf, len);
+			break;
+		case COPY_USER:
+			r = copy_from_user(send_buf, (const __user char*)buf,
+					   len);
+			break;
+		case COPY_NOCOPY:
+			sg_buf = buf;
+			break;
+		}
+	}
+
+	send_cmd->send_buf = send_buf;
+
+	sg = vmalloc_to_sg(sg_buf, len, send_cmd, len_cmd, &sg_elem);
+	if (!sg) {
+		printk(KERN_ERR "virtio-fb: couldn't gather scatter list\n");
+		return;
+	}
+
+add_again:
+	r = info->vq_out->vq_ops->add_buf(info->vq_out, sg, sg_elem, 0, send_cmd);
+	info->vq_out->vq_ops->kick(info->vq_out);
+
+	if ( r == -ENOSPC ) {
+		/* Queue was full, so try again */
+		cpu_relax();
+		virtio_fb_output(info->vq_out);
+		goto add_again;
+	}
+
+	kfree(sg);
+}
+
+static void virtio_fb_send_user(struct virtio_fb_info *info,
+				struct virtio_fb_cmd *cmd,
+				const __user char *buf, int len)
+{
+	_virtio_fb_send(info, cmd, (char *)buf, len, COPY_USER);
+}
+
+static void virtio_fb_send_nocopy(struct virtio_fb_info *info,
+				  struct virtio_fb_cmd *cmd,
+				  char *buf, int len)
+{
+	_virtio_fb_send(info, cmd, buf, len, COPY_NOCOPY);
+}
+
+static void virtio_fb_send(struct virtio_fb_info *info, struct virtio_fb_cmd *cmd,
+			   char *buf, int len)
+{
+	_virtio_fb_send(info, cmd, buf, len, COPY_KERNEL);
+}
+
+
+static int virtio_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
+			   unsigned blue, unsigned transp,
+			   struct fb_info *info)
+{
+	u32 v;
+	if (regno >= 16)
+		return 1;
+
+#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16)
+	red = CNVT_TOHW(red, info->var.red.length);
+	green = CNVT_TOHW(green, info->var.green.length);
+	blue = CNVT_TOHW(blue, info->var.blue.length);
+	transp = CNVT_TOHW(transp, info->var.transp.length);
+#undef CNVT_TOHW
+
+
+	v = (red << info->var.red.offset) |
+	    (green << info->var.green.offset) |
+	    (blue << info->var.blue.offset) |
+	    (transp << info->var.transp.offset);
+
+	((u32 *) (info->pseudo_palette))[regno] = v;
+
+	return 0;
+}
+
+static void virtio_fb_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
+{
+	struct virtio_fb_info *info = p->par;
+	struct virtio_fb_cmd cmd;
+
+	sys_fillrect(p, rect);
+
+	cmd.cmd = VIRTIO_FB_CMD_FILL;
+	cmd.fill.rop = rect->rop;
+	cmd.fill.x = rect->dx;
+	cmd.fill.y = rect->dy;
+	cmd.fill.width = rect->width;
+	cmd.fill.height = rect->height;
+	cmd.fill.color = rect->color;
+
+	virtio_fb_send(info, &cmd, NULL, 0);
+}
+
+static void virtio_fb_imageblit(struct fb_info *p, const struct fb_image *image)
+{
+	struct virtio_fb_info *info = p->par;
+	struct virtio_fb_cmd cmd;
+	char *_buf;
+	char *buf;
+	char *vfb;
+	int i, w;
+	int bpp = p->var.bits_per_pixel / 8;
+	int len = image->width * image->height * bpp;
+
+	sys_imageblit(p, image);
+
+	cmd.cmd = VIRTIO_FB_CMD_BLIT;
+	cmd.blit.x = image->dx;
+	cmd.blit.y = image->dy;
+	cmd.blit.height = image->height;
+	cmd.blit.width = image->width;
+
+	if (image->depth == 32) {
+		/* Send the image off directly */
+
+		virtio_fb_send(info, &cmd, (char*)image->data, len);
+		return;
+	}
+
+	/* Send the 32-bit translated image */
+
+	buf = _buf = vmalloc(len);
+
+	vfb = info->fb;
+	vfb += (image->dy * p->fix.line_length) + image->dx * bpp;
+
+	w = image->width * bpp;
+
+	for (i = 0; i < image->height; i++) {
+		memcpy(buf, vfb, w);
+
+		buf += w;
+		vfb += p->fix.line_length;
+	}
+
+	virtio_fb_send(info, &cmd, _buf, len);
+
+	vfree(_buf);
+}
+
+static void virtio_fb_copyarea(struct fb_info *p, const struct fb_copyarea *area)
+{
+	struct virtio_fb_info *info = p->par;
+	struct virtio_fb_cmd cmd;
+
+	sys_copyarea(p, area);
+
+	cmd.cmd = VIRTIO_FB_CMD_COPY;
+	cmd.copy_area.x1 = area->sx;
+	cmd.copy_area.y1 = area->sy;
+	cmd.copy_area.x2 = area->dx;
+	cmd.copy_area.y2 = area->dy;
+	cmd.copy_area.width = area->width;
+	cmd.copy_area.height = area->height;
+
+	virtio_fb_send(info, &cmd, NULL, 0);
+}
+
+static ssize_t virtio_fb_write(struct fb_info *p, const char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	struct virtio_fb_info *info = p->par;
+	struct virtio_fb_cmd cmd;
+	loff_t pos = *ppos;
+	ssize_t res;
+
+	res = fb_sys_write(p, buf, count, ppos);
+
+	cmd.cmd = VIRTIO_FB_CMD_WRITE;
+	cmd.write.offset = pos;
+	cmd.write.count = count;
+
+	virtio_fb_send_user(info, &cmd, buf, count);
+	return res;
+}
+
+static int
+virtio_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *p)
+{
+	struct virtio_fb_info *info = p->par;
+
+	if (var->bits_per_pixel != DEFAULT_DEPTH) {
+		/* We only support 32 bpp */
+		return -EINVAL;
+	}
+
+	if ((var->xres * var->yres * DEFAULT_DEPTH / 8) > info->size) {
+		/* Doesn't fit in the frame buffer */
+		return -EINVAL;
+	}
+
+	var->xres_virtual = var->xres;
+	var->yres_virtual = var->yres;
+
+	return 0;
+}
+
+static int virtio_fb_set_par(struct fb_info *p)
+{
+	struct virtio_fb_info *info = p->par;
+	struct virtio_fb_cmd cmd;
+
+	/* Do nothing if we're on that resolution already */
+	if ((p->var.xres == info->width) &&
+	    (p->var.yres == info->height))
+		return 0;
+
+	info->width = p->var.xres;
+	info->height = p->var.yres;
+	p->fix.line_length = p->var.xres_virtual * 4;
+
+	/* Notify hypervisor */
+
+	cmd.cmd = VIRTIO_FB_CMD_RESIZE;
+	cmd.resize.width = p->var.xres;
+	cmd.resize.height = p->var.yres;
+
+	virtio_fb_send(info, &cmd, NULL, 0);
+
+	return 0;
+}
+
+static struct fb_ops virtio_fb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_read	= fb_sys_read,
+	.fb_write	= virtio_fb_write,
+	.fb_setcolreg	= virtio_fb_setcolreg,
+	.fb_fillrect	= virtio_fb_fillrect,
+	.fb_copyarea	= virtio_fb_copyarea,
+	.fb_imageblit	= virtio_fb_imageblit,
+	.fb_check_var	= virtio_fb_check_var,
+	.fb_set_par     = virtio_fb_set_par,
+};
+
+static __devinit void
+virtio_fb_make_preferred_console(void)
+{
+	struct console *c;
+
+	if (console_set_on_cmdline)
+		return;
+
+	acquire_console_sem();
+	for (c = console_drivers; c; c = c->next) {
+		if (!strcmp(c->name, "tty") && c->index == 0)
+			break;
+	}
+	release_console_sem();
+	if (c) {
+		unregister_console(c);
+		c->flags |= CON_CONSDEV;
+		c->flags &= ~CON_PRINTBUFFER; /* don't print again */
+		register_console(c);
+	}
+}
+
+static void virtio_fb_deferred_io(struct fb_info *fb_info,
+				  struct list_head *pagelist)
+{
+	struct virtio_fb_info *info = fb_info->par;
+	struct page *page;
+	unsigned long beg, end;
+	int y1, y2, miny, maxy;
+	struct virtio_fb_cmd cmd;
+
+	miny = INT_MAX;
+	maxy = 0;
+	list_for_each_entry(page, pagelist, lru) {
+		beg = page->index << PAGE_SHIFT;
+		end = beg + PAGE_SIZE - 1;
+		y1 = beg / fb_info->fix.line_length;
+		y2 = end / fb_info->fix.line_length;
+		if (y2 >= fb_info->var.yres)
+			y2 = fb_info->var.yres - 1;
+		if (miny > y1)
+			miny = y1;
+		if (maxy < y2)
+			maxy = y2;
+	}
+
+	if (miny != INT_MAX) {
+		cmd.cmd = VIRTIO_FB_CMD_WRITE;
+		cmd.write.offset = miny * fb_info->fix.line_length;
+		cmd.write.count = (maxy - miny + 1) * fb_info->fix.line_length;
+
+		virtio_fb_send_nocopy(info, &cmd, info->fb + cmd.write.offset,
+				      cmd.write.count);
+	}
+}
+
+static struct fb_deferred_io virtio_fb_defio = {
+	.delay		= HZ / 20,
+	.deferred_io	= virtio_fb_deferred_io,
+};
+
+/* Callback when the host kicks our input queue.
+ *
+ * This is to enable notifications from host to guest. */
+static void virtio_fb_input(struct virtqueue *vq)
+{
+	struct virtio_fb_info *info = dev_get_drvdata(&vq->vdev->dev);
+	int len, i;
+	void *x;
+	void *reinject[3];
+	int reinject_count = 0;
+
+	while ((x = vq->vq_ops->get_buf(vq, &len)) != NULL) {
+		struct virtio_fb_cmd *cmd = x;
+
+		/* Make sure we're getting an inbuf page! */
+		BUG_ON((x != info->inbuf) &&
+		       (x != (info->inbuf + PAGE_SIZE)) &&
+		       (x != (info->inbuf + (PAGE_SIZE * 2))));
+
+		switch (cmd->cmd) {
+		case VIRTIO_FB_CMD_REFRESH:
+			schedule_work(&info->refresh_work);
+			break;
+		}
+
+		reinject[reinject_count++] = x;
+	}
+
+
+	for (i = 0; i < reinject_count; i++) {
+		struct scatterlist sg;
+		void *x = reinject[i];
+
+		sg_init_one(&sg, x, PAGE_SIZE);
+		vq->vq_ops->add_buf(vq, &sg, 0, 1, x);
+		vq->vq_ops->kick(vq);
+	}
+}
+
+/* Asynchronous snippet to send all screen contents to the host */
+static void deferred_refresh(struct work_struct *work)
+{
+	struct virtio_fb_info *info = container_of(work, struct virtio_fb_info,
+						   refresh_work);
+	struct virtio_fb_cmd cmd;
+
+	cmd.cmd = VIRTIO_FB_CMD_WRITE;
+	cmd.write.offset = 0;
+	cmd.write.count = info->width * info->height * 4;
+
+	virtio_fb_send_nocopy(info, &cmd, info->fb, cmd.write.count);
+}
+
+/* Asynchronous garbage collector :-) */
+static void deferred_vfree(struct work_struct *work)
+{
+	struct virtio_fb_info *info = container_of(work, struct virtio_fb_info,
+						   vfree_work);
+	int i;
+	unsigned long flags;
+	void *last_buf[MAX_BUF];
+	int idx;
+
+	spin_lock_irqsave(&info->vfree_lock, flags);
+
+	idx = info->last_buf_idx;
+	memcpy(last_buf, info->last_buf, sizeof(void*) * MAX_BUF);
+	info->last_buf_idx = 0;
+
+	spin_unlock_irqrestore(&info->vfree_lock, flags);
+
+	for (i = 0; i < idx; i++) {
+		vfree(last_buf[i]);
+	}
+}
+
+/* Callback when the host kicks our output queue. This can only mean it's done
+ * processing an item, so let's free up the memory occupied by the entries */
+static void virtio_fb_output(struct virtqueue *vq)
+{
+	struct virtio_fb_info *info = dev_get_drvdata(&vq->vdev->dev);
+	int len;
+	void *x;
+
+	while ((x = vq->vq_ops->get_buf(vq, &len)) != NULL) {
+		struct virtio_fb_cmd *cmd = x;
+
+		if (cmd->send_buf) {
+			spin_lock(&info->vfree_lock);
+			if (info->last_buf_idx != MAX_BUF) {
+				info->last_buf[info->last_buf_idx++] =
+					cmd->send_buf;
+			}
+			spin_unlock(&info->vfree_lock);
+
+			schedule_work(&info->vfree_work);
+		}
+
+		kmem_cache_free(info->cmd_cache, x);
+	}
+}
+
+static int __devinit virtio_fb_probe(struct virtio_device *dev)
+{
+	vq_callback_t *callbacks[] = { virtio_fb_input, virtio_fb_output };
+	const char *names[] = { "input", "output" };
+	struct virtio_fb_info *info;
+	struct virtqueue *vqs[2];
+	struct fb_info *fb_info = NULL;
+	int fb_size, res_size;
+	int ret, err, i;
+	char *inbuf;
+
+	err = dev->config->find_vqs(dev, 2, vqs, callbacks, names);
+	if (err) {
+		printk(KERN_ERR "VirtIO FB: couldn't find VQs\n");
+		return err;
+	}
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (info == NULL)
+		return -ENOMEM;
+
+	info->vq_in = vqs[0];
+	info->vq_out = vqs[1];
+
+	res_size = video[KPARAM_WIDTH] * video[KPARAM_HEIGHT] * DEFAULT_DEPTH / 8;
+	fb_size = video[KPARAM_MEM] * 1024 * 1024;
+
+	if (res_size > fb_size) {
+		video[KPARAM_WIDTH] = DEFAULT_WIDTH;
+		video[KPARAM_HEIGHT] = DEFAULT_HEIGHT;
+	}
+
+	dev_set_drvdata(&dev->dev, info);
+	info->vdev = dev;
+
+	info->fb = rvmalloc(fb_size);
+	if (info->fb == NULL)
+		goto error_nomem;
+
+	info->nr_pages = (fb_size + PAGE_SIZE - 1) >> PAGE_SHIFT;
+	info->size = fb_size;
+
+	fb_info = framebuffer_alloc(sizeof(u32) * 256, NULL);
+	if (fb_info == NULL)
+		goto error_nomem;
+
+	inbuf = kmalloc(PAGE_SIZE * 3, GFP_KERNEL);
+	info->inbuf = inbuf;
+	for (i = 0; i < (3 * PAGE_SIZE); i += PAGE_SIZE) {
+		struct scatterlist sg;
+
+		sg_init_one(&sg, inbuf + i, PAGE_SIZE);
+		info->vq_in->vq_ops->add_buf(info->vq_in, &sg, 0, 1, inbuf + i);
+	}
+	info->vq_in->vq_ops->kick(info->vq_in);
+
+	fb_info->pseudo_palette = fb_info->par;
+	fb_info->par = info;
+
+	fb_info->screen_base = info->fb;
+
+	fb_info->fbops = &virtio_fb_ops;
+	fb_info->var.xres_virtual = fb_info->var.xres = video[KPARAM_WIDTH];
+	fb_info->var.yres_virtual = fb_info->var.yres = video[KPARAM_HEIGHT];
+	fb_info->var.bits_per_pixel = DEFAULT_DEPTH;
+
+	fb_info->var.transp = (struct fb_bitfield){24, 8, 0};
+	fb_info->var.red    = (struct fb_bitfield){16, 8, 0};
+	fb_info->var.green  = (struct fb_bitfield){8,  8, 0};
+	fb_info->var.blue   = (struct fb_bitfield){0,  8, 0};
+
+	fb_info->var.activate = FB_ACTIVATE_NOW;
+	fb_info->var.height = -1;
+	fb_info->var.width = -1;
+	fb_info->var.vmode = FB_VMODE_NONINTERLACED;
+
+	fb_info->fix.visual = FB_VISUAL_TRUECOLOR;
+	fb_info->fix.line_length = fb_info->var.xres * DEFAULT_DEPTH / 8;
+	fb_info->fix.smem_start = 0;
+	fb_info->fix.smem_len = fb_size;
+	strcpy(fb_info->fix.id, "virtio_fb");
+	fb_info->fix.type = FB_TYPE_PACKED_PIXELS;
+	fb_info->fix.accel = FB_ACCEL_NONE;
+
+	fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_HWACCEL_COPYAREA |
+			 FBINFO_READS_FAST;
+
+	ret = fb_alloc_cmap(&fb_info->cmap, 256, 0);
+	if (ret < 0) {
+		framebuffer_release(fb_info);
+		goto error;
+	}
+
+	info->cmd_cache = kmem_cache_create("virtio_fb_cmd",
+					    sizeof(struct virtio_fb_cmd),
+					    0, 0, NULL);
+
+	if (!info->cmd_cache) {
+		framebuffer_release(fb_info);
+		goto error;
+	}
+
+	fb_info->fbdefio = &virtio_fb_defio;
+	fb_deferred_io_init(fb_info);
+
+	INIT_WORK(&info->refresh_work, deferred_refresh);
+	INIT_WORK(&info->vfree_work, deferred_vfree);
+	spin_lock_init(&info->vfree_lock);
+
+	ret = register_framebuffer(fb_info);
+	if (ret) {
+		fb_deferred_io_cleanup(fb_info);
+		fb_dealloc_cmap(&fb_info->cmap);
+		framebuffer_release(fb_info);
+		goto error;
+	}
+	info->fb_info = fb_info;
+
+	virtio_fb_make_preferred_console();
+	return 0;
+
+ error_nomem:
+	ret = -ENOMEM;
+ error:
+	framebuffer_release(fb_info);
+	return ret;
+}
+
+static void virtio_fb_apply_config(struct virtio_device *dev)
+{
+}
+
+static struct virtio_device_id id_table[] = {
+	{ VIRTIO_ID_FB, VIRTIO_DEV_ANY_ID },
+	{ 0 },
+};
+
+static unsigned int features[] = {
+};
+
+static struct virtio_driver virtio_console = {
+	.feature_table = features,
+	.feature_table_size = ARRAY_SIZE(features),
+	.driver.name =  KBUILD_MODNAME,
+	.driver.owner = THIS_MODULE,
+	.id_table =     id_table,
+	.probe =        virtio_fb_probe,
+	.config_changed = virtio_fb_apply_config,
+};
+
+static int __init init(void)
+{
+	return register_virtio_driver(&virtio_console);
+}
+module_init(init);
+
+
+MODULE_DEVICE_TABLE(virtio, id_table);
+MODULE_DESCRIPTION("Virtio framebuffer driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/virtio_ids.h b/include/linux/virtio_ids.h
index 06660c0..72e39f7 100644
--- a/include/linux/virtio_ids.h
+++ b/include/linux/virtio_ids.h
@@ -12,6 +12,7 @@ 
 #define VIRTIO_ID_CONSOLE	3 /* virtio console */
 #define VIRTIO_ID_RNG		4 /* virtio ring */
 #define VIRTIO_ID_BALLOON	5 /* virtio balloon */
+#define VIRTIO_ID_FB		6 /* virtio framebuffer */
 #define VIRTIO_ID_9P		9 /* 9p virtio console */
 
 #endif /* _LINUX_VIRTIO_IDS_H */