2008-04-03 01:54:07 +08:00
|
|
|
/*
|
|
|
|
* Xen para-virtual frame buffer device
|
|
|
|
*
|
|
|
|
* Copyright (C) 2005-2006 Anthony Liguori <aliguori@us.ibm.com>
|
|
|
|
* Copyright (C) 2006-2008 Red Hat, Inc., Markus Armbruster <armbru@redhat.com>
|
|
|
|
*
|
|
|
|
* Based on linux/drivers/video/q40fb.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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TODO:
|
|
|
|
*
|
|
|
|
* Switch to grant tables when they become capable of dealing with the
|
|
|
|
* frame buffer.
|
|
|
|
*/
|
|
|
|
|
xen: Enable console tty by default in domU if it's not a dummy
Without console= arguments on the kernel command line, the first
console to register becomes enabled and the preferred console (the one
behind /dev/console). This is normally tty (assuming
CONFIG_VT_CONSOLE is enabled, which it commonly is).
This is okay as long tty is a useful console. But unless we have the
PV framebuffer, and it is enabled for this domain, tty0 in domU is
merely a dummy. In that case, we want the preferred console to be the
Xen console hvc0, and we want it without having to fiddle with the
kernel command line. Commit b8c2d3dfbc117dff26058fbac316b8acfc2cb5f7
did that for us.
Since we now have the PV framebuffer, we want to enable and prefer tty
again, but only when PVFB is enabled. But even then we still want to
enable the Xen console as well.
Problem: when tty registers, we can't yet know whether the PVFB is
enabled. By the time we can know (xenstore is up), the console setup
game is over.
Solution: enable console tty by default, but keep hvc as the preferred
console. Change the preferred console to tty when PVFB probes
successfully, unless we've been given console kernel parameters.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
2008-05-27 06:31:07 +08:00
|
|
|
#include <linux/console.h>
|
2008-04-03 01:54:07 +08:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/fb.h>
|
|
|
|
#include <linux/module.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2008-04-03 01:54:07 +08:00
|
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <linux/mm.h>
|
2009-10-07 06:11:14 +08:00
|
|
|
|
2008-04-03 01:54:07 +08:00
|
|
|
#include <asm/xen/hypervisor.h>
|
2009-10-07 06:11:14 +08:00
|
|
|
|
|
|
|
#include <xen/xen.h>
|
2008-04-03 01:54:07 +08:00
|
|
|
#include <xen/events.h>
|
|
|
|
#include <xen/page.h>
|
|
|
|
#include <xen/interface/io/fbif.h>
|
|
|
|
#include <xen/interface/io/protocols.h>
|
|
|
|
#include <xen/xenbus.h>
|
|
|
|
|
|
|
|
struct xenfb_info {
|
|
|
|
unsigned char *fb;
|
|
|
|
struct fb_info *fb_info;
|
|
|
|
int x1, y1, x2, y2; /* dirty rectangle,
|
|
|
|
protected by dirty_lock */
|
|
|
|
spinlock_t dirty_lock;
|
|
|
|
int nr_pages;
|
|
|
|
int irq;
|
|
|
|
struct xenfb_page *page;
|
|
|
|
unsigned long *mfns;
|
|
|
|
int update_wanted; /* XENFB_TYPE_UPDATE wanted */
|
2008-05-27 06:31:11 +08:00
|
|
|
int feature_resize; /* XENFB_TYPE_RESIZE ok */
|
|
|
|
struct xenfb_resize resize; /* protected by resize_lock */
|
|
|
|
int resize_dpy; /* ditto */
|
|
|
|
spinlock_t resize_lock;
|
2008-04-03 01:54:07 +08:00
|
|
|
|
|
|
|
struct xenbus_device *xbdev;
|
|
|
|
};
|
|
|
|
|
2008-05-27 06:31:11 +08:00
|
|
|
#define XENFB_DEFAULT_FB_LEN (XENFB_WIDTH * XENFB_HEIGHT * XENFB_DEPTH / 8)
|
|
|
|
|
|
|
|
enum { KPARAM_MEM, KPARAM_WIDTH, KPARAM_HEIGHT, KPARAM_CNT };
|
|
|
|
static int video[KPARAM_CNT] = { 2, XENFB_WIDTH, XENFB_HEIGHT };
|
|
|
|
module_param_array(video, int, NULL, 0);
|
|
|
|
MODULE_PARM_DESC(video,
|
|
|
|
"Video memory size in MB, width, height in pixels (default 2,800,600)");
|
2008-04-03 01:54:07 +08:00
|
|
|
|
xen: Enable console tty by default in domU if it's not a dummy
Without console= arguments on the kernel command line, the first
console to register becomes enabled and the preferred console (the one
behind /dev/console). This is normally tty (assuming
CONFIG_VT_CONSOLE is enabled, which it commonly is).
This is okay as long tty is a useful console. But unless we have the
PV framebuffer, and it is enabled for this domain, tty0 in domU is
merely a dummy. In that case, we want the preferred console to be the
Xen console hvc0, and we want it without having to fiddle with the
kernel command line. Commit b8c2d3dfbc117dff26058fbac316b8acfc2cb5f7
did that for us.
Since we now have the PV framebuffer, we want to enable and prefer tty
again, but only when PVFB is enabled. But even then we still want to
enable the Xen console as well.
Problem: when tty registers, we can't yet know whether the PVFB is
enabled. By the time we can know (xenstore is up), the console setup
game is over.
Solution: enable console tty by default, but keep hvc as the preferred
console. Change the preferred console to tty when PVFB probes
successfully, unless we've been given console kernel parameters.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
2008-05-27 06:31:07 +08:00
|
|
|
static void xenfb_make_preferred_console(void);
|
2008-04-03 01:54:07 +08:00
|
|
|
static int xenfb_remove(struct xenbus_device *);
|
2008-05-27 06:31:11 +08:00
|
|
|
static void xenfb_init_shared_page(struct xenfb_info *, struct fb_info *);
|
2008-04-03 01:54:07 +08:00
|
|
|
static int xenfb_connect_backend(struct xenbus_device *, struct xenfb_info *);
|
|
|
|
static void xenfb_disconnect_backend(struct xenfb_info *);
|
|
|
|
|
2008-05-27 06:31:11 +08:00
|
|
|
static void xenfb_send_event(struct xenfb_info *info,
|
|
|
|
union xenfb_out_event *event)
|
|
|
|
{
|
|
|
|
u32 prod;
|
|
|
|
|
|
|
|
prod = info->page->out_prod;
|
|
|
|
/* caller ensures !xenfb_queue_full() */
|
|
|
|
mb(); /* ensure ring space available */
|
|
|
|
XENFB_OUT_RING_REF(info->page, prod) = *event;
|
|
|
|
wmb(); /* ensure ring contents visible */
|
|
|
|
info->page->out_prod = prod + 1;
|
|
|
|
|
|
|
|
notify_remote_via_irq(info->irq);
|
|
|
|
}
|
|
|
|
|
2008-04-03 01:54:07 +08:00
|
|
|
static void xenfb_do_update(struct xenfb_info *info,
|
|
|
|
int x, int y, int w, int h)
|
|
|
|
{
|
|
|
|
union xenfb_out_event event;
|
|
|
|
|
2008-05-27 06:31:10 +08:00
|
|
|
memset(&event, 0, sizeof(event));
|
2008-04-03 01:54:07 +08:00
|
|
|
event.type = XENFB_TYPE_UPDATE;
|
|
|
|
event.update.x = x;
|
|
|
|
event.update.y = y;
|
|
|
|
event.update.width = w;
|
|
|
|
event.update.height = h;
|
|
|
|
|
|
|
|
/* caller ensures !xenfb_queue_full() */
|
2008-05-27 06:31:11 +08:00
|
|
|
xenfb_send_event(info, &event);
|
|
|
|
}
|
2008-04-03 01:54:07 +08:00
|
|
|
|
2008-05-27 06:31:11 +08:00
|
|
|
static void xenfb_do_resize(struct xenfb_info *info)
|
|
|
|
{
|
|
|
|
union xenfb_out_event event;
|
|
|
|
|
|
|
|
memset(&event, 0, sizeof(event));
|
|
|
|
event.resize = info->resize;
|
|
|
|
|
|
|
|
/* caller ensures !xenfb_queue_full() */
|
|
|
|
xenfb_send_event(info, &event);
|
2008-04-03 01:54:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int xenfb_queue_full(struct xenfb_info *info)
|
|
|
|
{
|
|
|
|
u32 cons, prod;
|
|
|
|
|
|
|
|
prod = info->page->out_prod;
|
|
|
|
cons = info->page->out_cons;
|
|
|
|
return prod - cons == XENFB_OUT_RING_LEN;
|
|
|
|
}
|
|
|
|
|
2008-05-27 06:31:11 +08:00
|
|
|
static void xenfb_handle_resize_dpy(struct xenfb_info *info)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&info->resize_lock, flags);
|
|
|
|
if (info->resize_dpy) {
|
|
|
|
if (!xenfb_queue_full(info)) {
|
|
|
|
info->resize_dpy = 0;
|
|
|
|
xenfb_do_resize(info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spin_unlock_irqrestore(&info->resize_lock, flags);
|
|
|
|
}
|
|
|
|
|
2008-04-03 01:54:07 +08:00
|
|
|
static void xenfb_refresh(struct xenfb_info *info,
|
|
|
|
int x1, int y1, int w, int h)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
int x2 = x1 + w - 1;
|
2008-05-27 06:31:11 +08:00
|
|
|
int y2 = y1 + h - 1;
|
|
|
|
|
|
|
|
xenfb_handle_resize_dpy(info);
|
2008-04-03 01:54:07 +08:00
|
|
|
|
|
|
|
if (!info->update_wanted)
|
|
|
|
return;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&info->dirty_lock, flags);
|
|
|
|
|
|
|
|
/* Combine with dirty rectangle: */
|
|
|
|
if (info->y1 < y1)
|
|
|
|
y1 = info->y1;
|
|
|
|
if (info->y2 > y2)
|
|
|
|
y2 = info->y2;
|
|
|
|
if (info->x1 < x1)
|
|
|
|
x1 = info->x1;
|
|
|
|
if (info->x2 > x2)
|
|
|
|
x2 = info->x2;
|
|
|
|
|
|
|
|
if (xenfb_queue_full(info)) {
|
|
|
|
/* Can't send right now, stash it in the dirty rectangle */
|
|
|
|
info->x1 = x1;
|
|
|
|
info->x2 = x2;
|
|
|
|
info->y1 = y1;
|
|
|
|
info->y2 = y2;
|
|
|
|
spin_unlock_irqrestore(&info->dirty_lock, flags);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clear dirty rectangle: */
|
|
|
|
info->x1 = info->y1 = INT_MAX;
|
|
|
|
info->x2 = info->y2 = 0;
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&info->dirty_lock, flags);
|
|
|
|
|
|
|
|
if (x1 <= x2 && y1 <= y2)
|
|
|
|
xenfb_do_update(info, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xenfb_deferred_io(struct fb_info *fb_info,
|
|
|
|
struct list_head *pagelist)
|
|
|
|
{
|
|
|
|
struct xenfb_info *info = fb_info->par;
|
|
|
|
struct page *page;
|
|
|
|
unsigned long beg, end;
|
|
|
|
int y1, y2, miny, maxy;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
xenfb_refresh(info, 0, miny, fb_info->var.xres, maxy - miny + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct fb_deferred_io xenfb_defio = {
|
|
|
|
.delay = HZ / 20,
|
|
|
|
.deferred_io = xenfb_deferred_io,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int xenfb_setcolreg(unsigned regno, unsigned red, unsigned green,
|
|
|
|
unsigned blue, unsigned transp,
|
|
|
|
struct fb_info *info)
|
|
|
|
{
|
|
|
|
u32 v;
|
|
|
|
|
|
|
|
if (regno > info->cmap.len)
|
|
|
|
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);
|
|
|
|
|
|
|
|
switch (info->var.bits_per_pixel) {
|
|
|
|
case 16:
|
|
|
|
case 24:
|
|
|
|
case 32:
|
|
|
|
((u32 *)info->pseudo_palette)[regno] = v;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xenfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
|
|
|
|
{
|
|
|
|
struct xenfb_info *info = p->par;
|
|
|
|
|
|
|
|
sys_fillrect(p, rect);
|
|
|
|
xenfb_refresh(info, rect->dx, rect->dy, rect->width, rect->height);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xenfb_imageblit(struct fb_info *p, const struct fb_image *image)
|
|
|
|
{
|
|
|
|
struct xenfb_info *info = p->par;
|
|
|
|
|
|
|
|
sys_imageblit(p, image);
|
|
|
|
xenfb_refresh(info, image->dx, image->dy, image->width, image->height);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xenfb_copyarea(struct fb_info *p, const struct fb_copyarea *area)
|
|
|
|
{
|
|
|
|
struct xenfb_info *info = p->par;
|
|
|
|
|
|
|
|
sys_copyarea(p, area);
|
|
|
|
xenfb_refresh(info, area->dx, area->dy, area->width, area->height);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t xenfb_write(struct fb_info *p, const char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
|
|
|
struct xenfb_info *info = p->par;
|
|
|
|
ssize_t res;
|
|
|
|
|
|
|
|
res = fb_sys_write(p, buf, count, ppos);
|
|
|
|
xenfb_refresh(info, 0, 0, info->page->width, info->page->height);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2008-05-27 06:31:11 +08:00
|
|
|
static int
|
|
|
|
xenfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct xenfb_info *xenfb_info;
|
|
|
|
int required_mem_len;
|
|
|
|
|
|
|
|
xenfb_info = info->par;
|
|
|
|
|
|
|
|
if (!xenfb_info->feature_resize) {
|
|
|
|
if (var->xres == video[KPARAM_WIDTH] &&
|
|
|
|
var->yres == video[KPARAM_HEIGHT] &&
|
|
|
|
var->bits_per_pixel == xenfb_info->page->depth) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't resize past initial width and height */
|
|
|
|
if (var->xres > video[KPARAM_WIDTH] || var->yres > video[KPARAM_HEIGHT])
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
required_mem_len = var->xres * var->yres * xenfb_info->page->depth / 8;
|
|
|
|
if (var->bits_per_pixel == xenfb_info->page->depth &&
|
|
|
|
var->xres <= info->fix.line_length / (XENFB_DEPTH / 8) &&
|
|
|
|
required_mem_len <= info->fix.smem_len) {
|
|
|
|
var->xres_virtual = var->xres;
|
|
|
|
var->yres_virtual = var->yres;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xenfb_set_par(struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct xenfb_info *xenfb_info;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
xenfb_info = info->par;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&xenfb_info->resize_lock, flags);
|
|
|
|
xenfb_info->resize.type = XENFB_TYPE_RESIZE;
|
|
|
|
xenfb_info->resize.width = info->var.xres;
|
|
|
|
xenfb_info->resize.height = info->var.yres;
|
|
|
|
xenfb_info->resize.stride = info->fix.line_length;
|
|
|
|
xenfb_info->resize.depth = info->var.bits_per_pixel;
|
|
|
|
xenfb_info->resize.offset = 0;
|
|
|
|
xenfb_info->resize_dpy = 1;
|
|
|
|
spin_unlock_irqrestore(&xenfb_info->resize_lock, flags);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-04-03 01:54:07 +08:00
|
|
|
static struct fb_ops xenfb_fb_ops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.fb_read = fb_sys_read,
|
|
|
|
.fb_write = xenfb_write,
|
|
|
|
.fb_setcolreg = xenfb_setcolreg,
|
|
|
|
.fb_fillrect = xenfb_fillrect,
|
|
|
|
.fb_copyarea = xenfb_copyarea,
|
|
|
|
.fb_imageblit = xenfb_imageblit,
|
2008-05-27 06:31:11 +08:00
|
|
|
.fb_check_var = xenfb_check_var,
|
|
|
|
.fb_set_par = xenfb_set_par,
|
2008-04-03 01:54:07 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static irqreturn_t xenfb_event_handler(int rq, void *dev_id)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* No in events recognized, simply ignore them all.
|
|
|
|
* If you need to recognize some, see xen-kbdfront's
|
|
|
|
* input_handler() for how to do that.
|
|
|
|
*/
|
|
|
|
struct xenfb_info *info = dev_id;
|
|
|
|
struct xenfb_page *page = info->page;
|
|
|
|
|
|
|
|
if (page->in_cons != page->in_prod) {
|
|
|
|
info->page->in_cons = info->page->in_prod;
|
|
|
|
notify_remote_via_irq(info->irq);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Flush dirty rectangle: */
|
|
|
|
xenfb_refresh(info, INT_MAX, INT_MAX, -INT_MAX, -INT_MAX);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __devinit xenfb_probe(struct xenbus_device *dev,
|
|
|
|
const struct xenbus_device_id *id)
|
|
|
|
{
|
|
|
|
struct xenfb_info *info;
|
|
|
|
struct fb_info *fb_info;
|
2008-05-27 06:31:11 +08:00
|
|
|
int fb_size;
|
|
|
|
int val;
|
2008-04-03 01:54:07 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
|
|
|
if (info == NULL) {
|
|
|
|
xenbus_dev_fatal(dev, -ENOMEM, "allocating info structure");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2008-05-27 06:31:11 +08:00
|
|
|
|
|
|
|
/* Limit kernel param videoram amount to what is in xenstore */
|
|
|
|
if (xenbus_scanf(XBT_NIL, dev->otherend, "videoram", "%d", &val) == 1) {
|
|
|
|
if (val < video[KPARAM_MEM])
|
|
|
|
video[KPARAM_MEM] = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If requested res does not fit in available memory, use default */
|
|
|
|
fb_size = video[KPARAM_MEM] * 1024 * 1024;
|
|
|
|
if (video[KPARAM_WIDTH] * video[KPARAM_HEIGHT] * XENFB_DEPTH / 8
|
|
|
|
> fb_size) {
|
|
|
|
video[KPARAM_WIDTH] = XENFB_WIDTH;
|
|
|
|
video[KPARAM_HEIGHT] = XENFB_HEIGHT;
|
|
|
|
fb_size = XENFB_DEFAULT_FB_LEN;
|
|
|
|
}
|
|
|
|
|
2009-05-05 03:40:54 +08:00
|
|
|
dev_set_drvdata(&dev->dev, info);
|
2008-04-03 01:54:07 +08:00
|
|
|
info->xbdev = dev;
|
|
|
|
info->irq = -1;
|
|
|
|
info->x1 = info->y1 = INT_MAX;
|
|
|
|
spin_lock_init(&info->dirty_lock);
|
2008-05-27 06:31:11 +08:00
|
|
|
spin_lock_init(&info->resize_lock);
|
2008-04-03 01:54:07 +08:00
|
|
|
|
2008-05-27 06:31:11 +08:00
|
|
|
info->fb = vmalloc(fb_size);
|
2008-04-03 01:54:07 +08:00
|
|
|
if (info->fb == NULL)
|
|
|
|
goto error_nomem;
|
2008-05-27 06:31:11 +08:00
|
|
|
memset(info->fb, 0, fb_size);
|
2008-04-03 01:54:07 +08:00
|
|
|
|
2008-05-27 06:31:11 +08:00
|
|
|
info->nr_pages = (fb_size + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
2008-04-03 01:54:07 +08:00
|
|
|
|
|
|
|
info->mfns = vmalloc(sizeof(unsigned long) * info->nr_pages);
|
|
|
|
if (!info->mfns)
|
|
|
|
goto error_nomem;
|
|
|
|
|
|
|
|
/* set up shared page */
|
|
|
|
info->page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
|
|
|
|
if (!info->page)
|
|
|
|
goto error_nomem;
|
|
|
|
|
|
|
|
/* abusing framebuffer_alloc() to allocate pseudo_palette */
|
|
|
|
fb_info = framebuffer_alloc(sizeof(u32) * 256, NULL);
|
|
|
|
if (fb_info == NULL)
|
|
|
|
goto error_nomem;
|
|
|
|
|
|
|
|
/* complete the abuse: */
|
|
|
|
fb_info->pseudo_palette = fb_info->par;
|
|
|
|
fb_info->par = info;
|
|
|
|
|
|
|
|
fb_info->screen_base = info->fb;
|
|
|
|
|
|
|
|
fb_info->fbops = &xenfb_fb_ops;
|
2008-05-27 06:31:11 +08:00
|
|
|
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 = XENFB_DEPTH;
|
2008-04-03 01:54:07 +08:00
|
|
|
|
|
|
|
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;
|
2008-05-27 06:31:11 +08:00
|
|
|
fb_info->fix.line_length = fb_info->var.xres * XENFB_DEPTH / 8;
|
2008-04-03 01:54:07 +08:00
|
|
|
fb_info->fix.smem_start = 0;
|
2008-05-27 06:31:11 +08:00
|
|
|
fb_info->fix.smem_len = fb_size;
|
2008-04-03 01:54:07 +08:00
|
|
|
strcpy(fb_info->fix.id, "xen");
|
|
|
|
fb_info->fix.type = FB_TYPE_PACKED_PIXELS;
|
|
|
|
fb_info->fix.accel = FB_ACCEL_NONE;
|
|
|
|
|
2009-12-03 23:31:57 +08:00
|
|
|
fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB;
|
2008-04-03 01:54:07 +08:00
|
|
|
|
|
|
|
ret = fb_alloc_cmap(&fb_info->cmap, 256, 0);
|
|
|
|
if (ret < 0) {
|
|
|
|
framebuffer_release(fb_info);
|
|
|
|
xenbus_dev_fatal(dev, ret, "fb_alloc_cmap");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
fb_info->fbdefio = &xenfb_defio;
|
|
|
|
fb_deferred_io_init(fb_info);
|
|
|
|
|
2008-05-27 06:31:11 +08:00
|
|
|
xenfb_init_shared_page(info, fb_info);
|
|
|
|
|
2009-08-28 03:22:43 +08:00
|
|
|
ret = xenfb_connect_backend(dev, info);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
2008-04-03 01:54:07 +08:00
|
|
|
ret = register_framebuffer(fb_info);
|
|
|
|
if (ret) {
|
|
|
|
fb_deferred_io_cleanup(fb_info);
|
|
|
|
fb_dealloc_cmap(&fb_info->cmap);
|
|
|
|
framebuffer_release(fb_info);
|
|
|
|
xenbus_dev_fatal(dev, ret, "register_framebuffer");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
info->fb_info = fb_info;
|
|
|
|
|
xen: Enable console tty by default in domU if it's not a dummy
Without console= arguments on the kernel command line, the first
console to register becomes enabled and the preferred console (the one
behind /dev/console). This is normally tty (assuming
CONFIG_VT_CONSOLE is enabled, which it commonly is).
This is okay as long tty is a useful console. But unless we have the
PV framebuffer, and it is enabled for this domain, tty0 in domU is
merely a dummy. In that case, we want the preferred console to be the
Xen console hvc0, and we want it without having to fiddle with the
kernel command line. Commit b8c2d3dfbc117dff26058fbac316b8acfc2cb5f7
did that for us.
Since we now have the PV framebuffer, we want to enable and prefer tty
again, but only when PVFB is enabled. But even then we still want to
enable the Xen console as well.
Problem: when tty registers, we can't yet know whether the PVFB is
enabled. By the time we can know (xenstore is up), the console setup
game is over.
Solution: enable console tty by default, but keep hvc as the preferred
console. Change the preferred console to tty when PVFB probes
successfully, unless we've been given console kernel parameters.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
2008-05-27 06:31:07 +08:00
|
|
|
xenfb_make_preferred_console();
|
2008-04-03 01:54:07 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
error_nomem:
|
|
|
|
ret = -ENOMEM;
|
|
|
|
xenbus_dev_fatal(dev, ret, "allocating device memory");
|
|
|
|
error:
|
|
|
|
xenfb_remove(dev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
xen: Enable console tty by default in domU if it's not a dummy
Without console= arguments on the kernel command line, the first
console to register becomes enabled and the preferred console (the one
behind /dev/console). This is normally tty (assuming
CONFIG_VT_CONSOLE is enabled, which it commonly is).
This is okay as long tty is a useful console. But unless we have the
PV framebuffer, and it is enabled for this domain, tty0 in domU is
merely a dummy. In that case, we want the preferred console to be the
Xen console hvc0, and we want it without having to fiddle with the
kernel command line. Commit b8c2d3dfbc117dff26058fbac316b8acfc2cb5f7
did that for us.
Since we now have the PV framebuffer, we want to enable and prefer tty
again, but only when PVFB is enabled. But even then we still want to
enable the Xen console as well.
Problem: when tty registers, we can't yet know whether the PVFB is
enabled. By the time we can know (xenstore is up), the console setup
game is over.
Solution: enable console tty by default, but keep hvc as the preferred
console. Change the preferred console to tty when PVFB probes
successfully, unless we've been given console kernel parameters.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
2008-05-27 06:31:07 +08:00
|
|
|
static __devinit void
|
|
|
|
xenfb_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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-03 01:54:07 +08:00
|
|
|
static int xenfb_resume(struct xenbus_device *dev)
|
|
|
|
{
|
2009-05-05 03:40:54 +08:00
|
|
|
struct xenfb_info *info = dev_get_drvdata(&dev->dev);
|
2008-04-03 01:54:07 +08:00
|
|
|
|
|
|
|
xenfb_disconnect_backend(info);
|
2008-05-27 06:31:11 +08:00
|
|
|
xenfb_init_shared_page(info, info->fb_info);
|
2008-04-03 01:54:07 +08:00
|
|
|
return xenfb_connect_backend(dev, info);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xenfb_remove(struct xenbus_device *dev)
|
|
|
|
{
|
2009-05-05 03:40:54 +08:00
|
|
|
struct xenfb_info *info = dev_get_drvdata(&dev->dev);
|
2008-04-03 01:54:07 +08:00
|
|
|
|
|
|
|
xenfb_disconnect_backend(info);
|
|
|
|
if (info->fb_info) {
|
|
|
|
fb_deferred_io_cleanup(info->fb_info);
|
|
|
|
unregister_framebuffer(info->fb_info);
|
|
|
|
fb_dealloc_cmap(&info->fb_info->cmap);
|
|
|
|
framebuffer_release(info->fb_info);
|
|
|
|
}
|
|
|
|
free_page((unsigned long)info->page);
|
|
|
|
vfree(info->mfns);
|
|
|
|
vfree(info->fb);
|
|
|
|
kfree(info);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned long vmalloc_to_mfn(void *address)
|
|
|
|
{
|
|
|
|
return pfn_to_mfn(vmalloc_to_pfn(address));
|
|
|
|
}
|
|
|
|
|
2008-05-27 06:31:11 +08:00
|
|
|
static void xenfb_init_shared_page(struct xenfb_info *info,
|
|
|
|
struct fb_info *fb_info)
|
2008-04-03 01:54:07 +08:00
|
|
|
{
|
|
|
|
int i;
|
2008-05-27 06:31:11 +08:00
|
|
|
int epd = PAGE_SIZE / sizeof(info->mfns[0]);
|
2008-04-03 01:54:07 +08:00
|
|
|
|
|
|
|
for (i = 0; i < info->nr_pages; i++)
|
|
|
|
info->mfns[i] = vmalloc_to_mfn(info->fb + i * PAGE_SIZE);
|
|
|
|
|
2008-05-27 06:31:11 +08:00
|
|
|
for (i = 0; i * epd < info->nr_pages; i++)
|
|
|
|
info->page->pd[i] = vmalloc_to_mfn(&info->mfns[i * epd]);
|
|
|
|
|
|
|
|
info->page->width = fb_info->var.xres;
|
|
|
|
info->page->height = fb_info->var.yres;
|
|
|
|
info->page->depth = fb_info->var.bits_per_pixel;
|
|
|
|
info->page->line_length = fb_info->fix.line_length;
|
|
|
|
info->page->mem_length = fb_info->fix.smem_len;
|
2008-04-03 01:54:07 +08:00
|
|
|
info->page->in_cons = info->page->in_prod = 0;
|
|
|
|
info->page->out_cons = info->page->out_prod = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xenfb_connect_backend(struct xenbus_device *dev,
|
|
|
|
struct xenfb_info *info)
|
|
|
|
{
|
|
|
|
int ret, evtchn;
|
|
|
|
struct xenbus_transaction xbt;
|
|
|
|
|
|
|
|
ret = xenbus_alloc_evtchn(dev, &evtchn);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
ret = bind_evtchn_to_irqhandler(evtchn, xenfb_event_handler,
|
|
|
|
0, dev->devicetype, info);
|
|
|
|
if (ret < 0) {
|
|
|
|
xenbus_free_evtchn(dev, evtchn);
|
|
|
|
xenbus_dev_fatal(dev, ret, "bind_evtchn_to_irqhandler");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
info->irq = ret;
|
|
|
|
|
|
|
|
again:
|
|
|
|
ret = xenbus_transaction_start(&xbt);
|
|
|
|
if (ret) {
|
|
|
|
xenbus_dev_fatal(dev, ret, "starting transaction");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
ret = xenbus_printf(xbt, dev->nodename, "page-ref", "%lu",
|
|
|
|
virt_to_mfn(info->page));
|
|
|
|
if (ret)
|
|
|
|
goto error_xenbus;
|
|
|
|
ret = xenbus_printf(xbt, dev->nodename, "event-channel", "%u",
|
|
|
|
evtchn);
|
|
|
|
if (ret)
|
|
|
|
goto error_xenbus;
|
|
|
|
ret = xenbus_printf(xbt, dev->nodename, "protocol", "%s",
|
|
|
|
XEN_IO_PROTO_ABI_NATIVE);
|
|
|
|
if (ret)
|
|
|
|
goto error_xenbus;
|
|
|
|
ret = xenbus_printf(xbt, dev->nodename, "feature-update", "1");
|
|
|
|
if (ret)
|
|
|
|
goto error_xenbus;
|
|
|
|
ret = xenbus_transaction_end(xbt, 0);
|
|
|
|
if (ret) {
|
|
|
|
if (ret == -EAGAIN)
|
|
|
|
goto again;
|
|
|
|
xenbus_dev_fatal(dev, ret, "completing transaction");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
xenbus_switch_state(dev, XenbusStateInitialised);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error_xenbus:
|
|
|
|
xenbus_transaction_end(xbt, 1);
|
|
|
|
xenbus_dev_fatal(dev, ret, "writing xenstore");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xenfb_disconnect_backend(struct xenfb_info *info)
|
|
|
|
{
|
|
|
|
if (info->irq >= 0)
|
|
|
|
unbind_from_irqhandler(info->irq, info);
|
|
|
|
info->irq = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xenfb_backend_changed(struct xenbus_device *dev,
|
|
|
|
enum xenbus_state backend_state)
|
|
|
|
{
|
2009-05-05 03:40:54 +08:00
|
|
|
struct xenfb_info *info = dev_get_drvdata(&dev->dev);
|
2008-04-03 01:54:07 +08:00
|
|
|
int val;
|
|
|
|
|
|
|
|
switch (backend_state) {
|
|
|
|
case XenbusStateInitialising:
|
|
|
|
case XenbusStateInitialised:
|
|
|
|
case XenbusStateUnknown:
|
|
|
|
case XenbusStateClosed:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XenbusStateInitWait:
|
|
|
|
InitWait:
|
|
|
|
xenbus_switch_state(dev, XenbusStateConnected);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XenbusStateConnected:
|
|
|
|
/*
|
|
|
|
* Work around xenbus race condition: If backend goes
|
|
|
|
* through InitWait to Connected fast enough, we can
|
|
|
|
* get Connected twice here.
|
|
|
|
*/
|
|
|
|
if (dev->state != XenbusStateConnected)
|
|
|
|
goto InitWait; /* no InitWait seen yet, fudge it */
|
|
|
|
|
|
|
|
if (xenbus_scanf(XBT_NIL, info->xbdev->otherend,
|
|
|
|
"request-update", "%d", &val) < 0)
|
|
|
|
val = 0;
|
|
|
|
if (val)
|
|
|
|
info->update_wanted = 1;
|
2008-05-27 06:31:11 +08:00
|
|
|
|
|
|
|
if (xenbus_scanf(XBT_NIL, dev->otherend,
|
|
|
|
"feature-resize", "%d", &val) < 0)
|
|
|
|
val = 0;
|
|
|
|
info->feature_resize = val;
|
2008-04-03 01:54:07 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case XenbusStateClosing:
|
|
|
|
xenbus_frontend_closed(dev);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct xenbus_device_id xenfb_ids[] = {
|
|
|
|
{ "vfb" },
|
|
|
|
{ "" }
|
|
|
|
};
|
|
|
|
|
2008-11-23 01:38:14 +08:00
|
|
|
static struct xenbus_driver xenfb_driver = {
|
2008-04-03 01:54:07 +08:00
|
|
|
.name = "vfb",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.ids = xenfb_ids,
|
|
|
|
.probe = xenfb_probe,
|
|
|
|
.remove = xenfb_remove,
|
|
|
|
.resume = xenfb_resume,
|
|
|
|
.otherend_changed = xenfb_backend_changed,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init xenfb_init(void)
|
|
|
|
{
|
2008-08-20 04:16:17 +08:00
|
|
|
if (!xen_domain())
|
2008-04-03 01:54:07 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
/* Nothing to do if running in dom0. */
|
2008-08-20 04:16:17 +08:00
|
|
|
if (xen_initial_domain())
|
2008-04-03 01:54:07 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
2008-11-23 01:38:14 +08:00
|
|
|
return xenbus_register_frontend(&xenfb_driver);
|
2008-04-03 01:54:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit xenfb_cleanup(void)
|
|
|
|
{
|
2008-11-23 01:38:14 +08:00
|
|
|
xenbus_unregister_driver(&xenfb_driver);
|
2008-04-03 01:54:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(xenfb_init);
|
|
|
|
module_exit(xenfb_cleanup);
|
|
|
|
|
2008-05-27 06:31:09 +08:00
|
|
|
MODULE_DESCRIPTION("Xen virtual framebuffer device frontend");
|
2008-04-03 01:54:07 +08:00
|
|
|
MODULE_LICENSE("GPL");
|
2008-05-27 06:31:09 +08:00
|
|
|
MODULE_ALIAS("xen:vfb");
|