2007-10-16 15:28:26 +07:00
|
|
|
/*
|
|
|
|
* A framebuffer driver for VBE 2.0+ compliant video cards
|
|
|
|
*
|
|
|
|
* (c) 2007 Michal Januszewski <spock@gentoo.org>
|
|
|
|
* Loosely based upon the vesafb driver.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/timer.h>
|
|
|
|
#include <linux/completion.h>
|
|
|
|
#include <linux/connector.h>
|
|
|
|
#include <linux/random.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/limits.h>
|
|
|
|
#include <linux/fb.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/mutex.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 15:04:11 +07:00
|
|
|
#include <linux/slab.h>
|
2007-10-16 15:28:26 +07:00
|
|
|
#include <video/edid.h>
|
|
|
|
#include <video/uvesafb.h>
|
|
|
|
#ifdef CONFIG_X86
|
|
|
|
#include <video/vga.h>
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_MTRR
|
|
|
|
#include <asm/mtrr.h>
|
|
|
|
#endif
|
|
|
|
#include "edid.h"
|
|
|
|
|
|
|
|
static struct cb_id uvesafb_cn_id = {
|
|
|
|
.idx = CN_IDX_V86D,
|
|
|
|
.val = CN_VAL_V86D_UVESAFB
|
|
|
|
};
|
|
|
|
static char v86d_path[PATH_MAX] = "/sbin/v86d";
|
|
|
|
static char v86d_started; /* has v86d been started by uvesafb? */
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static struct fb_fix_screeninfo uvesafb_fix = {
|
2007-10-16 15:28:26 +07:00
|
|
|
.id = "VESA VGA",
|
|
|
|
.type = FB_TYPE_PACKED_PIXELS,
|
|
|
|
.accel = FB_ACCEL_NONE,
|
|
|
|
.visual = FB_VISUAL_TRUECOLOR,
|
|
|
|
};
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static int mtrr = 3; /* enable mtrr by default */
|
|
|
|
static bool blank = 1; /* enable blanking by default */
|
|
|
|
static int ypan = 1; /* 0: scroll, 1: ypan, 2: ywrap */
|
|
|
|
static bool pmi_setpal = true; /* use PMI for palette changes */
|
|
|
|
static bool nocrtc; /* ignore CRTC settings */
|
|
|
|
static bool noedid; /* don't try DDC transfers */
|
|
|
|
static int vram_remap; /* set amt. of memory to be used */
|
|
|
|
static int vram_total; /* set total amount of memory */
|
|
|
|
static u16 maxclk; /* maximum pixel clock */
|
|
|
|
static u16 maxvf; /* maximum vertical frequency */
|
|
|
|
static u16 maxhf; /* maximum horizontal frequency */
|
|
|
|
static u16 vbemode; /* force use of a specific VBE mode */
|
|
|
|
static char *mode_option;
|
2009-04-14 04:39:43 +07:00
|
|
|
static u8 dac_width = 6;
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
static struct uvesafb_ktask *uvfb_tasks[UVESAFB_TASKS_MAX];
|
|
|
|
static DEFINE_MUTEX(uvfb_lock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A handler for replies from userspace.
|
|
|
|
*
|
|
|
|
* Make sure each message passes consistency checks and if it does,
|
|
|
|
* find the kernel part of the task struct, copy the registers and
|
|
|
|
* the buffer contents and then complete the task.
|
|
|
|
*/
|
2009-10-02 09:40:05 +07:00
|
|
|
static void uvesafb_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
struct uvesafb_task *utask;
|
|
|
|
struct uvesafb_ktask *task;
|
|
|
|
|
2012-05-04 18:34:03 +07:00
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
2009-10-02 09:40:11 +07:00
|
|
|
return;
|
|
|
|
|
2007-10-16 15:28:26 +07:00
|
|
|
if (msg->seq >= UVESAFB_TASKS_MAX)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mutex_lock(&uvfb_lock);
|
|
|
|
task = uvfb_tasks[msg->seq];
|
|
|
|
|
|
|
|
if (!task || msg->ack != task->ack) {
|
|
|
|
mutex_unlock(&uvfb_lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
utask = (struct uvesafb_task *)msg->data;
|
|
|
|
|
|
|
|
/* Sanity checks for the buffer length. */
|
|
|
|
if (task->t.buf_len < utask->buf_len ||
|
|
|
|
utask->buf_len > msg->len - sizeof(*utask)) {
|
|
|
|
mutex_unlock(&uvfb_lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
uvfb_tasks[msg->seq] = NULL;
|
|
|
|
mutex_unlock(&uvfb_lock);
|
|
|
|
|
|
|
|
memcpy(&task->t, utask, sizeof(*utask));
|
|
|
|
|
|
|
|
if (task->t.buf_len && task->buf)
|
|
|
|
memcpy(task->buf, utask + 1, task->t.buf_len);
|
|
|
|
|
|
|
|
complete(task->done);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_helper_start(void)
|
|
|
|
{
|
|
|
|
char *envp[] = {
|
|
|
|
"HOME=/",
|
|
|
|
"PATH=/sbin:/bin",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
char *argv[] = {
|
|
|
|
v86d_path,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2012-03-24 05:02:46 +07:00
|
|
|
return call_usermodehelper(v86d_path, argv, envp, UMH_WAIT_PROC);
|
2007-10-16 15:28:26 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Execute a uvesafb task.
|
|
|
|
*
|
|
|
|
* Returns 0 if the task is executed successfully.
|
|
|
|
*
|
|
|
|
* A message sent to the userspace consists of the uvesafb_task
|
|
|
|
* struct and (optionally) a buffer. The uvesafb_task struct is
|
|
|
|
* a simplified version of uvesafb_ktask (its kernel counterpart)
|
|
|
|
* containing only the register values, flags and the length of
|
|
|
|
* the buffer.
|
|
|
|
*
|
|
|
|
* Each message is assigned a sequence number (increased linearly)
|
|
|
|
* and a random ack number. The sequence number is used as a key
|
|
|
|
* for the uvfb_tasks array which holds pointers to uvesafb_ktask
|
|
|
|
* structs for all requests.
|
|
|
|
*/
|
|
|
|
static int uvesafb_exec(struct uvesafb_ktask *task)
|
|
|
|
{
|
|
|
|
static int seq;
|
|
|
|
struct cn_msg *m;
|
|
|
|
int err;
|
|
|
|
int len = sizeof(task->t) + task->t.buf_len;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check whether the message isn't longer than the maximum
|
|
|
|
* allowed by connector.
|
|
|
|
*/
|
|
|
|
if (sizeof(*m) + len > CONNECTOR_MAX_MSG_SIZE) {
|
|
|
|
printk(KERN_WARNING "uvesafb: message too long (%d), "
|
|
|
|
"can't execute task\n", (int)(sizeof(*m) + len));
|
|
|
|
return -E2BIG;
|
|
|
|
}
|
|
|
|
|
|
|
|
m = kzalloc(sizeof(*m) + len, GFP_KERNEL);
|
|
|
|
if (!m)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
init_completion(task->done);
|
|
|
|
|
|
|
|
memcpy(&m->id, &uvesafb_cn_id, sizeof(m->id));
|
|
|
|
m->seq = seq;
|
|
|
|
m->len = len;
|
2013-04-30 06:21:32 +07:00
|
|
|
m->ack = prandom_u32();
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
/* uvesafb_task structure */
|
|
|
|
memcpy(m + 1, &task->t, sizeof(task->t));
|
|
|
|
|
|
|
|
/* Buffer */
|
|
|
|
memcpy((u8 *)(m + 1) + sizeof(task->t), task->buf, task->t.buf_len);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Save the message ack number so that we can find the kernel
|
|
|
|
* part of this task when a reply is received from userspace.
|
|
|
|
*/
|
|
|
|
task->ack = m->ack;
|
|
|
|
|
|
|
|
mutex_lock(&uvfb_lock);
|
|
|
|
|
|
|
|
/* If all slots are taken -- bail out. */
|
|
|
|
if (uvfb_tasks[seq]) {
|
|
|
|
mutex_unlock(&uvfb_lock);
|
2008-04-28 16:15:41 +07:00
|
|
|
err = -EBUSY;
|
|
|
|
goto out;
|
2007-10-16 15:28:26 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Save a pointer to the kernel part of the task struct. */
|
|
|
|
uvfb_tasks[seq] = task;
|
|
|
|
mutex_unlock(&uvfb_lock);
|
|
|
|
|
2009-04-01 05:25:01 +07:00
|
|
|
err = cn_netlink_send(m, 0, GFP_KERNEL);
|
2007-10-16 15:28:26 +07:00
|
|
|
if (err == -ESRCH) {
|
|
|
|
/*
|
|
|
|
* Try to start the userspace helper if sending
|
|
|
|
* the request failed the first time.
|
|
|
|
*/
|
|
|
|
err = uvesafb_helper_start();
|
|
|
|
if (err) {
|
|
|
|
printk(KERN_ERR "uvesafb: failed to execute %s\n",
|
|
|
|
v86d_path);
|
|
|
|
printk(KERN_ERR "uvesafb: make sure that the v86d "
|
|
|
|
"helper is installed and executable\n");
|
|
|
|
} else {
|
|
|
|
v86d_started = 1;
|
|
|
|
err = cn_netlink_send(m, 0, gfp_any());
|
netlink: change return-value logic of netlink_broadcast()
Currently, netlink_broadcast() reports errors to the caller if no
messages at all were delivered:
1) If, at least, one message has been delivered correctly, returns 0.
2) Otherwise, if no messages at all were delivered due to skb_clone()
failure, return -ENOBUFS.
3) Otherwise, if there are no listeners, return -ESRCH.
With this patch, the caller knows if the delivery of any of the
messages to the listeners have failed:
1) If it fails to deliver any message (for whatever reason), return
-ENOBUFS.
2) Otherwise, if all messages were delivered OK, returns 0.
3) Otherwise, if no listeners, return -ESRCH.
In the current ctnetlink code and in Netfilter in general, we can add
reliable logging and connection tracking event delivery by dropping the
packets whose events were not successfully delivered over Netlink. Of
course, this option would be settable via /proc as this approach reduces
performance (in terms of filtered connections per seconds by a stateful
firewall) but providing reliable logging and event delivery (for
conntrackd) in return.
This patch also changes some clients of netlink_broadcast() that
may report ENOBUFS errors via printk. This error handling is not
of any help. Instead, the userspace daemons that are listening to
those netlink messages should resync themselves with the kernel-side
if they hit ENOBUFS.
BTW, netlink_broadcast() clients include those that call
cn_netlink_send(), nlmsg_multicast() and genlmsg_multicast() since they
internally call netlink_broadcast() and return its error value.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2009-02-06 14:56:36 +07:00
|
|
|
if (err == -ENOBUFS)
|
|
|
|
err = 0;
|
2007-10-16 15:28:26 +07:00
|
|
|
}
|
netlink: change return-value logic of netlink_broadcast()
Currently, netlink_broadcast() reports errors to the caller if no
messages at all were delivered:
1) If, at least, one message has been delivered correctly, returns 0.
2) Otherwise, if no messages at all were delivered due to skb_clone()
failure, return -ENOBUFS.
3) Otherwise, if there are no listeners, return -ESRCH.
With this patch, the caller knows if the delivery of any of the
messages to the listeners have failed:
1) If it fails to deliver any message (for whatever reason), return
-ENOBUFS.
2) Otherwise, if all messages were delivered OK, returns 0.
3) Otherwise, if no listeners, return -ESRCH.
In the current ctnetlink code and in Netfilter in general, we can add
reliable logging and connection tracking event delivery by dropping the
packets whose events were not successfully delivered over Netlink. Of
course, this option would be settable via /proc as this approach reduces
performance (in terms of filtered connections per seconds by a stateful
firewall) but providing reliable logging and event delivery (for
conntrackd) in return.
This patch also changes some clients of netlink_broadcast() that
may report ENOBUFS errors via printk. This error handling is not
of any help. Instead, the userspace daemons that are listening to
those netlink messages should resync themselves with the kernel-side
if they hit ENOBUFS.
BTW, netlink_broadcast() clients include those that call
cn_netlink_send(), nlmsg_multicast() and genlmsg_multicast() since they
internally call netlink_broadcast() and return its error value.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2009-02-06 14:56:36 +07:00
|
|
|
} else if (err == -ENOBUFS)
|
|
|
|
err = 0;
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
if (!err && !(task->t.flags & TF_EXIT))
|
|
|
|
err = !wait_for_completion_timeout(task->done,
|
|
|
|
msecs_to_jiffies(UVESAFB_TIMEOUT));
|
|
|
|
|
|
|
|
mutex_lock(&uvfb_lock);
|
|
|
|
uvfb_tasks[seq] = NULL;
|
|
|
|
mutex_unlock(&uvfb_lock);
|
|
|
|
|
|
|
|
seq++;
|
|
|
|
if (seq >= UVESAFB_TASKS_MAX)
|
|
|
|
seq = 0;
|
2008-04-28 16:15:41 +07:00
|
|
|
out:
|
|
|
|
kfree(m);
|
2007-10-16 15:28:26 +07:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Free a uvesafb_ktask struct.
|
|
|
|
*/
|
|
|
|
static void uvesafb_free(struct uvesafb_ktask *task)
|
|
|
|
{
|
|
|
|
if (task) {
|
|
|
|
if (task->done)
|
|
|
|
kfree(task->done);
|
|
|
|
kfree(task);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Prepare a uvesafb_ktask struct to be used again.
|
|
|
|
*/
|
|
|
|
static void uvesafb_reset(struct uvesafb_ktask *task)
|
|
|
|
{
|
|
|
|
struct completion *cpl = task->done;
|
|
|
|
|
|
|
|
memset(task, 0, sizeof(*task));
|
|
|
|
task->done = cpl;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate and prepare a uvesafb_ktask struct.
|
|
|
|
*/
|
|
|
|
static struct uvesafb_ktask *uvesafb_prep(void)
|
|
|
|
{
|
|
|
|
struct uvesafb_ktask *task;
|
|
|
|
|
|
|
|
task = kzalloc(sizeof(*task), GFP_KERNEL);
|
|
|
|
if (task) {
|
|
|
|
task->done = kzalloc(sizeof(*task->done), GFP_KERNEL);
|
|
|
|
if (!task->done) {
|
|
|
|
kfree(task);
|
|
|
|
task = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return task;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void uvesafb_setup_var(struct fb_var_screeninfo *var,
|
|
|
|
struct fb_info *info, struct vbe_mode_ib *mode)
|
|
|
|
{
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
var->vmode = FB_VMODE_NONINTERLACED;
|
|
|
|
var->sync = FB_SYNC_VERT_HIGH_ACT;
|
|
|
|
|
|
|
|
var->xres = mode->x_res;
|
|
|
|
var->yres = mode->y_res;
|
|
|
|
var->xres_virtual = mode->x_res;
|
|
|
|
var->yres_virtual = (par->ypan) ?
|
|
|
|
info->fix.smem_len / mode->bytes_per_scan_line :
|
|
|
|
mode->y_res;
|
|
|
|
var->xoffset = 0;
|
|
|
|
var->yoffset = 0;
|
|
|
|
var->bits_per_pixel = mode->bits_per_pixel;
|
|
|
|
|
|
|
|
if (var->bits_per_pixel == 15)
|
|
|
|
var->bits_per_pixel = 16;
|
|
|
|
|
|
|
|
if (var->bits_per_pixel > 8) {
|
|
|
|
var->red.offset = mode->red_off;
|
|
|
|
var->red.length = mode->red_len;
|
|
|
|
var->green.offset = mode->green_off;
|
|
|
|
var->green.length = mode->green_len;
|
|
|
|
var->blue.offset = mode->blue_off;
|
|
|
|
var->blue.length = mode->blue_len;
|
|
|
|
var->transp.offset = mode->rsvd_off;
|
|
|
|
var->transp.length = mode->rsvd_len;
|
|
|
|
} else {
|
|
|
|
var->red.offset = 0;
|
|
|
|
var->green.offset = 0;
|
|
|
|
var->blue.offset = 0;
|
|
|
|
var->transp.offset = 0;
|
|
|
|
|
2009-04-14 04:39:43 +07:00
|
|
|
var->red.length = 8;
|
|
|
|
var->green.length = 8;
|
|
|
|
var->blue.length = 8;
|
|
|
|
var->transp.length = 0;
|
2007-10-16 15:28:26 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_vbe_find_mode(struct uvesafb_par *par,
|
|
|
|
int xres, int yres, int depth, unsigned char flags)
|
|
|
|
{
|
|
|
|
int i, match = -1, h = 0, d = 0x7fffffff;
|
|
|
|
|
|
|
|
for (i = 0; i < par->vbe_modes_cnt; i++) {
|
|
|
|
h = abs(par->vbe_modes[i].x_res - xres) +
|
|
|
|
abs(par->vbe_modes[i].y_res - yres) +
|
|
|
|
abs(depth - par->vbe_modes[i].depth);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We have an exact match in terms of resolution
|
|
|
|
* and depth.
|
|
|
|
*/
|
|
|
|
if (h == 0)
|
|
|
|
return i;
|
|
|
|
|
|
|
|
if (h < d || (h == d && par->vbe_modes[i].depth > depth)) {
|
|
|
|
d = h;
|
|
|
|
match = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i = 1;
|
|
|
|
|
|
|
|
if (flags & UVESAFB_EXACT_DEPTH &&
|
|
|
|
par->vbe_modes[match].depth != depth)
|
|
|
|
i = 0;
|
|
|
|
|
|
|
|
if (flags & UVESAFB_EXACT_RES && d > 24)
|
|
|
|
i = 0;
|
|
|
|
|
|
|
|
if (i != 0)
|
|
|
|
return match;
|
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u8 *uvesafb_vbe_state_save(struct uvesafb_par *par)
|
|
|
|
{
|
|
|
|
struct uvesafb_ktask *task;
|
|
|
|
u8 *state;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!par->vbe_state_size)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
state = kmalloc(par->vbe_state_size, GFP_KERNEL);
|
|
|
|
if (!state)
|
2012-03-02 08:45:31 +07:00
|
|
|
return ERR_PTR(-ENOMEM);
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
task = uvesafb_prep();
|
|
|
|
if (!task) {
|
|
|
|
kfree(state);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
task->t.regs.eax = 0x4f04;
|
|
|
|
task->t.regs.ecx = 0x000f;
|
|
|
|
task->t.regs.edx = 0x0001;
|
|
|
|
task->t.flags = TF_BUF_RET | TF_BUF_ESBX;
|
|
|
|
task->t.buf_len = par->vbe_state_size;
|
|
|
|
task->buf = state;
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
|
|
|
|
if (err || (task->t.regs.eax & 0xffff) != 0x004f) {
|
|
|
|
printk(KERN_WARNING "uvesafb: VBE get state call "
|
|
|
|
"failed (eax=0x%x, err=%d)\n",
|
|
|
|
task->t.regs.eax, err);
|
|
|
|
kfree(state);
|
|
|
|
state = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
uvesafb_free(task);
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void uvesafb_vbe_state_restore(struct uvesafb_par *par, u8 *state_buf)
|
|
|
|
{
|
|
|
|
struct uvesafb_ktask *task;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!state_buf)
|
|
|
|
return;
|
|
|
|
|
|
|
|
task = uvesafb_prep();
|
|
|
|
if (!task)
|
|
|
|
return;
|
|
|
|
|
|
|
|
task->t.regs.eax = 0x4f04;
|
|
|
|
task->t.regs.ecx = 0x000f;
|
|
|
|
task->t.regs.edx = 0x0002;
|
|
|
|
task->t.buf_len = par->vbe_state_size;
|
|
|
|
task->t.flags = TF_BUF_ESBX;
|
|
|
|
task->buf = state_buf;
|
|
|
|
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
if (err || (task->t.regs.eax & 0xffff) != 0x004f)
|
|
|
|
printk(KERN_WARNING "uvesafb: VBE state restore call "
|
|
|
|
"failed (eax=0x%x, err=%d)\n",
|
|
|
|
task->t.regs.eax, err);
|
|
|
|
|
|
|
|
uvesafb_free(task);
|
|
|
|
}
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static int uvesafb_vbe_getinfo(struct uvesafb_ktask *task,
|
|
|
|
struct uvesafb_par *par)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
task->t.regs.eax = 0x4f00;
|
|
|
|
task->t.flags = TF_VBEIB;
|
|
|
|
task->t.buf_len = sizeof(struct vbe_ib);
|
|
|
|
task->buf = &par->vbe_ib;
|
|
|
|
strncpy(par->vbe_ib.vbe_signature, "VBE2", 4);
|
|
|
|
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
if (err || (task->t.regs.eax & 0xffff) != 0x004f) {
|
|
|
|
printk(KERN_ERR "uvesafb: Getting VBE info block failed "
|
|
|
|
"(eax=0x%x, err=%d)\n", (u32)task->t.regs.eax,
|
|
|
|
err);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (par->vbe_ib.vbe_version < 0x0200) {
|
|
|
|
printk(KERN_ERR "uvesafb: Sorry, pre-VBE 2.0 cards are "
|
|
|
|
"not supported.\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!par->vbe_ib.mode_list_ptr) {
|
|
|
|
printk(KERN_ERR "uvesafb: Missing mode list!\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
printk(KERN_INFO "uvesafb: ");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert string pointers and the mode list pointer into
|
|
|
|
* usable addresses. Print informational messages about the
|
|
|
|
* video adapter and its vendor.
|
|
|
|
*/
|
|
|
|
if (par->vbe_ib.oem_vendor_name_ptr)
|
|
|
|
printk("%s, ",
|
|
|
|
((char *)task->buf) + par->vbe_ib.oem_vendor_name_ptr);
|
|
|
|
|
|
|
|
if (par->vbe_ib.oem_product_name_ptr)
|
|
|
|
printk("%s, ",
|
|
|
|
((char *)task->buf) + par->vbe_ib.oem_product_name_ptr);
|
|
|
|
|
|
|
|
if (par->vbe_ib.oem_product_rev_ptr)
|
|
|
|
printk("%s, ",
|
|
|
|
((char *)task->buf) + par->vbe_ib.oem_product_rev_ptr);
|
|
|
|
|
|
|
|
if (par->vbe_ib.oem_string_ptr)
|
|
|
|
printk("OEM: %s, ",
|
|
|
|
((char *)task->buf) + par->vbe_ib.oem_string_ptr);
|
|
|
|
|
|
|
|
printk("VBE v%d.%d\n", ((par->vbe_ib.vbe_version & 0xff00) >> 8),
|
|
|
|
par->vbe_ib.vbe_version & 0xff);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static int uvesafb_vbe_getmodes(struct uvesafb_ktask *task,
|
|
|
|
struct uvesafb_par *par)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
int off = 0, err;
|
|
|
|
u16 *mode;
|
|
|
|
|
|
|
|
par->vbe_modes_cnt = 0;
|
|
|
|
|
|
|
|
/* Count available modes. */
|
|
|
|
mode = (u16 *) (((u8 *)&par->vbe_ib) + par->vbe_ib.mode_list_ptr);
|
|
|
|
while (*mode != 0xffff) {
|
|
|
|
par->vbe_modes_cnt++;
|
|
|
|
mode++;
|
|
|
|
}
|
|
|
|
|
|
|
|
par->vbe_modes = kzalloc(sizeof(struct vbe_mode_ib) *
|
|
|
|
par->vbe_modes_cnt, GFP_KERNEL);
|
|
|
|
if (!par->vbe_modes)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* Get info about all available modes. */
|
|
|
|
mode = (u16 *) (((u8 *)&par->vbe_ib) + par->vbe_ib.mode_list_ptr);
|
|
|
|
while (*mode != 0xffff) {
|
|
|
|
struct vbe_mode_ib *mib;
|
|
|
|
|
|
|
|
uvesafb_reset(task);
|
|
|
|
task->t.regs.eax = 0x4f01;
|
|
|
|
task->t.regs.ecx = (u32) *mode;
|
|
|
|
task->t.flags = TF_BUF_RET | TF_BUF_ESDI;
|
|
|
|
task->t.buf_len = sizeof(struct vbe_mode_ib);
|
|
|
|
task->buf = par->vbe_modes + off;
|
|
|
|
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
if (err || (task->t.regs.eax & 0xffff) != 0x004f) {
|
2008-10-16 12:03:47 +07:00
|
|
|
printk(KERN_WARNING "uvesafb: Getting mode info block "
|
2007-10-16 15:28:26 +07:00
|
|
|
"for mode 0x%x failed (eax=0x%x, err=%d)\n",
|
|
|
|
*mode, (u32)task->t.regs.eax, err);
|
2008-10-16 12:03:47 +07:00
|
|
|
mode++;
|
|
|
|
par->vbe_modes_cnt--;
|
|
|
|
continue;
|
2007-10-16 15:28:26 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
mib = task->buf;
|
|
|
|
mib->mode_id = *mode;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We only want modes that are supported with the current
|
|
|
|
* hardware configuration, color, graphics and that have
|
|
|
|
* support for the LFB.
|
|
|
|
*/
|
|
|
|
if ((mib->mode_attr & VBE_MODE_MASK) == VBE_MODE_MASK &&
|
|
|
|
mib->bits_per_pixel >= 8)
|
|
|
|
off++;
|
|
|
|
else
|
|
|
|
par->vbe_modes_cnt--;
|
|
|
|
|
|
|
|
mode++;
|
|
|
|
mib->depth = mib->red_len + mib->green_len + mib->blue_len;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle 8bpp modes and modes with broken color component
|
|
|
|
* lengths.
|
|
|
|
*/
|
|
|
|
if (mib->depth == 0 || (mib->depth == 24 &&
|
|
|
|
mib->bits_per_pixel == 32))
|
|
|
|
mib->depth = mib->bits_per_pixel;
|
|
|
|
}
|
|
|
|
|
2008-10-16 12:03:47 +07:00
|
|
|
if (par->vbe_modes_cnt > 0)
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
return -EINVAL;
|
2007-10-16 15:28:26 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The Protected Mode Interface is 32-bit x86 code, so we only run it on
|
|
|
|
* x86 and not x86_64.
|
|
|
|
*/
|
|
|
|
#ifdef CONFIG_X86_32
|
2012-12-22 04:07:39 +07:00
|
|
|
static int uvesafb_vbe_getpmi(struct uvesafb_ktask *task,
|
|
|
|
struct uvesafb_par *par)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
int i, err;
|
|
|
|
|
|
|
|
uvesafb_reset(task);
|
|
|
|
task->t.regs.eax = 0x4f0a;
|
|
|
|
task->t.regs.ebx = 0x0;
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
|
|
|
|
if ((task->t.regs.eax & 0xffff) != 0x4f || task->t.regs.es < 0xc000) {
|
|
|
|
par->pmi_setpal = par->ypan = 0;
|
|
|
|
} else {
|
|
|
|
par->pmi_base = (u16 *)phys_to_virt(((u32)task->t.regs.es << 4)
|
|
|
|
+ task->t.regs.edi);
|
|
|
|
par->pmi_start = (u8 *)par->pmi_base + par->pmi_base[1];
|
|
|
|
par->pmi_pal = (u8 *)par->pmi_base + par->pmi_base[2];
|
|
|
|
printk(KERN_INFO "uvesafb: protected mode interface info at "
|
|
|
|
"%04x:%04x\n",
|
|
|
|
(u16)task->t.regs.es, (u16)task->t.regs.edi);
|
|
|
|
printk(KERN_INFO "uvesafb: pmi: set display start = %p, "
|
|
|
|
"set palette = %p\n", par->pmi_start,
|
|
|
|
par->pmi_pal);
|
|
|
|
|
|
|
|
if (par->pmi_base[3]) {
|
|
|
|
printk(KERN_INFO "uvesafb: pmi: ports = ");
|
|
|
|
for (i = par->pmi_base[3]/2;
|
|
|
|
par->pmi_base[i] != 0xffff; i++)
|
|
|
|
printk("%x ", par->pmi_base[i]);
|
|
|
|
printk("\n");
|
|
|
|
|
|
|
|
if (par->pmi_base[i] != 0xffff) {
|
|
|
|
printk(KERN_INFO "uvesafb: can't handle memory"
|
|
|
|
" requests, pmi disabled\n");
|
|
|
|
par->ypan = par->pmi_setpal = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_X86_32 */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check whether a video mode is supported by the Video BIOS and is
|
|
|
|
* compatible with the monitor limits.
|
|
|
|
*/
|
2012-12-22 04:07:39 +07:00
|
|
|
static int uvesafb_is_valid_mode(struct fb_videomode *mode,
|
|
|
|
struct fb_info *info)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
if (info->monspecs.gtf) {
|
|
|
|
fb_videomode_to_var(&info->var, mode);
|
|
|
|
if (fb_validate_mode(&info->var, info))
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uvesafb_vbe_find_mode(info->par, mode->xres, mode->yres, 8,
|
|
|
|
UVESAFB_EXACT_RES) == -1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static int uvesafb_vbe_getedid(struct uvesafb_ktask *task, struct fb_info *info)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
if (noedid || par->vbe_ib.vbe_version < 0x0300)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
task->t.regs.eax = 0x4f15;
|
|
|
|
task->t.regs.ebx = 0;
|
|
|
|
task->t.regs.ecx = 0;
|
|
|
|
task->t.buf_len = 0;
|
|
|
|
task->t.flags = 0;
|
|
|
|
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
|
|
|
|
if ((task->t.regs.eax & 0xffff) != 0x004f || err)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if ((task->t.regs.ebx & 0x3) == 3) {
|
|
|
|
printk(KERN_INFO "uvesafb: VBIOS/hardware supports both "
|
|
|
|
"DDC1 and DDC2 transfers\n");
|
|
|
|
} else if ((task->t.regs.ebx & 0x3) == 2) {
|
|
|
|
printk(KERN_INFO "uvesafb: VBIOS/hardware supports DDC2 "
|
|
|
|
"transfers\n");
|
|
|
|
} else if ((task->t.regs.ebx & 0x3) == 1) {
|
|
|
|
printk(KERN_INFO "uvesafb: VBIOS/hardware supports DDC1 "
|
|
|
|
"transfers\n");
|
|
|
|
} else {
|
|
|
|
printk(KERN_INFO "uvesafb: VBIOS/hardware doesn't support "
|
|
|
|
"DDC transfers\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
task->t.regs.eax = 0x4f15;
|
|
|
|
task->t.regs.ebx = 1;
|
|
|
|
task->t.regs.ecx = task->t.regs.edx = 0;
|
|
|
|
task->t.flags = TF_BUF_RET | TF_BUF_ESDI;
|
|
|
|
task->t.buf_len = EDID_LENGTH;
|
|
|
|
task->buf = kzalloc(EDID_LENGTH, GFP_KERNEL);
|
2012-08-13 17:02:32 +07:00
|
|
|
if (!task->buf)
|
|
|
|
return -ENOMEM;
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
|
|
|
|
if ((task->t.regs.eax & 0xffff) == 0x004f && !err) {
|
|
|
|
fb_edid_to_monspecs(task->buf, &info->monspecs);
|
|
|
|
|
|
|
|
if (info->monspecs.vfmax && info->monspecs.hfmax) {
|
|
|
|
/*
|
|
|
|
* If the maximum pixel clock wasn't specified in
|
|
|
|
* the EDID block, set it to 300 MHz.
|
|
|
|
*/
|
|
|
|
if (info->monspecs.dclkmax == 0)
|
|
|
|
info->monspecs.dclkmax = 300 * 1000000;
|
|
|
|
info->monspecs.gtf = 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err = -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(task->buf);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static void uvesafb_vbe_getmonspecs(struct uvesafb_ktask *task,
|
|
|
|
struct fb_info *info)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
memset(&info->monspecs, 0, sizeof(info->monspecs));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we don't get all necessary data from the EDID block,
|
|
|
|
* mark it as incompatible with the GTF and set nocrtc so
|
|
|
|
* that we always use the default BIOS refresh rate.
|
|
|
|
*/
|
|
|
|
if (uvesafb_vbe_getedid(task, info)) {
|
|
|
|
info->monspecs.gtf = 0;
|
|
|
|
par->nocrtc = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Kernel command line overrides. */
|
|
|
|
if (maxclk)
|
|
|
|
info->monspecs.dclkmax = maxclk * 1000000;
|
|
|
|
if (maxvf)
|
|
|
|
info->monspecs.vfmax = maxvf;
|
|
|
|
if (maxhf)
|
|
|
|
info->monspecs.hfmax = maxhf * 1000;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In case DDC transfers are not supported, the user can provide
|
|
|
|
* monitor limits manually. Lower limits are set to "safe" values.
|
|
|
|
*/
|
|
|
|
if (info->monspecs.gtf == 0 && maxclk && maxvf && maxhf) {
|
|
|
|
info->monspecs.dclkmin = 0;
|
|
|
|
info->monspecs.vfmin = 60;
|
|
|
|
info->monspecs.hfmin = 29000;
|
|
|
|
info->monspecs.gtf = 1;
|
|
|
|
par->nocrtc = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->monspecs.gtf)
|
|
|
|
printk(KERN_INFO
|
|
|
|
"uvesafb: monitor limits: vf = %d Hz, hf = %d kHz, "
|
|
|
|
"clk = %d MHz\n", info->monspecs.vfmax,
|
|
|
|
(int)(info->monspecs.hfmax / 1000),
|
|
|
|
(int)(info->monspecs.dclkmax / 1000000));
|
|
|
|
else
|
|
|
|
printk(KERN_INFO "uvesafb: no monitor limits have been set, "
|
|
|
|
"default refresh rate will be used\n");
|
|
|
|
|
|
|
|
/* Add VBE modes to the modelist. */
|
|
|
|
for (i = 0; i < par->vbe_modes_cnt; i++) {
|
|
|
|
struct fb_var_screeninfo var;
|
|
|
|
struct vbe_mode_ib *mode;
|
|
|
|
struct fb_videomode vmode;
|
|
|
|
|
|
|
|
mode = &par->vbe_modes[i];
|
|
|
|
memset(&var, 0, sizeof(var));
|
|
|
|
|
|
|
|
var.xres = mode->x_res;
|
|
|
|
var.yres = mode->y_res;
|
|
|
|
|
|
|
|
fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60, &var, info);
|
|
|
|
fb_var_to_videomode(&vmode, &var);
|
|
|
|
fb_add_videomode(&vmode, &info->modelist);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add valid VESA modes to our modelist. */
|
|
|
|
for (i = 0; i < VESA_MODEDB_SIZE; i++) {
|
|
|
|
if (uvesafb_is_valid_mode((struct fb_videomode *)
|
|
|
|
&vesa_modes[i], info))
|
|
|
|
fb_add_videomode(&vesa_modes[i], &info->modelist);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < info->monspecs.modedb_len; i++) {
|
|
|
|
if (uvesafb_is_valid_mode(&info->monspecs.modedb[i], info))
|
|
|
|
fb_add_videomode(&info->monspecs.modedb[i],
|
|
|
|
&info->modelist);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static void uvesafb_vbe_getstatesize(struct uvesafb_ktask *task,
|
|
|
|
struct uvesafb_par *par)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
uvesafb_reset(task);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the VBE state buffer size. We want all available
|
|
|
|
* hardware state data (CL = 0x0f).
|
|
|
|
*/
|
|
|
|
task->t.regs.eax = 0x4f04;
|
|
|
|
task->t.regs.ecx = 0x000f;
|
|
|
|
task->t.regs.edx = 0x0000;
|
|
|
|
task->t.flags = 0;
|
|
|
|
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
|
|
|
|
if (err || (task->t.regs.eax & 0xffff) != 0x004f) {
|
|
|
|
printk(KERN_WARNING "uvesafb: VBE state buffer size "
|
|
|
|
"cannot be determined (eax=0x%x, err=%d)\n",
|
|
|
|
task->t.regs.eax, err);
|
|
|
|
par->vbe_state_size = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
par->vbe_state_size = 64 * (task->t.regs.ebx & 0xffff);
|
|
|
|
}
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static int uvesafb_vbe_init(struct fb_info *info)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
struct uvesafb_ktask *task = NULL;
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
task = uvesafb_prep();
|
|
|
|
if (!task)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
err = uvesafb_vbe_getinfo(task, par);
|
|
|
|
if (err)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
err = uvesafb_vbe_getmodes(task, par);
|
|
|
|
if (err)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
par->nocrtc = nocrtc;
|
|
|
|
#ifdef CONFIG_X86_32
|
|
|
|
par->pmi_setpal = pmi_setpal;
|
|
|
|
par->ypan = ypan;
|
|
|
|
|
2012-04-01 07:54:02 +07:00
|
|
|
if (par->pmi_setpal || par->ypan) {
|
|
|
|
if (__supported_pte_mask & _PAGE_NX) {
|
|
|
|
par->pmi_setpal = par->ypan = 0;
|
|
|
|
printk(KERN_WARNING "uvesafb: NX protection is actively."
|
|
|
|
"We have better not to use the PMI.\n");
|
|
|
|
} else {
|
|
|
|
uvesafb_vbe_getpmi(task, par);
|
|
|
|
}
|
|
|
|
}
|
2007-10-16 15:28:26 +07:00
|
|
|
#else
|
|
|
|
/* The protected mode interface is not available on non-x86. */
|
|
|
|
par->pmi_setpal = par->ypan = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&info->modelist);
|
|
|
|
uvesafb_vbe_getmonspecs(task, info);
|
|
|
|
uvesafb_vbe_getstatesize(task, par);
|
|
|
|
|
|
|
|
out: uvesafb_free(task);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static int uvesafb_vbe_init_mode(struct fb_info *info)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
struct list_head *pos;
|
|
|
|
struct fb_modelist *modelist;
|
|
|
|
struct fb_videomode *mode;
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
int i, modeid;
|
|
|
|
|
|
|
|
/* Has the user requested a specific VESA mode? */
|
|
|
|
if (vbemode) {
|
|
|
|
for (i = 0; i < par->vbe_modes_cnt; i++) {
|
|
|
|
if (par->vbe_modes[i].mode_id == vbemode) {
|
2009-04-01 05:25:41 +07:00
|
|
|
modeid = i;
|
|
|
|
uvesafb_setup_var(&info->var, info,
|
|
|
|
&par->vbe_modes[modeid]);
|
2007-10-16 15:28:26 +07:00
|
|
|
fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60,
|
2009-04-01 05:25:41 +07:00
|
|
|
&info->var, info);
|
2007-10-16 15:28:26 +07:00
|
|
|
/*
|
|
|
|
* With pixclock set to 0, the default BIOS
|
|
|
|
* timings will be used in set_par().
|
|
|
|
*/
|
|
|
|
info->var.pixclock = 0;
|
|
|
|
goto gotmode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printk(KERN_INFO "uvesafb: requested VBE mode 0x%x is "
|
|
|
|
"unavailable\n", vbemode);
|
|
|
|
vbemode = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Count the modes in the modelist */
|
|
|
|
i = 0;
|
|
|
|
list_for_each(pos, &info->modelist)
|
|
|
|
i++;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert the modelist into a modedb so that we can use it with
|
|
|
|
* fb_find_mode().
|
|
|
|
*/
|
|
|
|
mode = kzalloc(i * sizeof(*mode), GFP_KERNEL);
|
|
|
|
if (mode) {
|
|
|
|
i = 0;
|
|
|
|
list_for_each(pos, &info->modelist) {
|
|
|
|
modelist = list_entry(pos, struct fb_modelist, list);
|
|
|
|
mode[i] = modelist->mode;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mode_option)
|
|
|
|
mode_option = UVESAFB_DEFAULT_MODE;
|
|
|
|
|
|
|
|
i = fb_find_mode(&info->var, info, mode_option, mode, i,
|
|
|
|
NULL, 8);
|
|
|
|
|
|
|
|
kfree(mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fb_find_mode() failed */
|
2008-04-28 16:15:15 +07:00
|
|
|
if (i == 0) {
|
2007-10-16 15:28:26 +07:00
|
|
|
info->var.xres = 640;
|
|
|
|
info->var.yres = 480;
|
|
|
|
mode = (struct fb_videomode *)
|
|
|
|
fb_find_best_mode(&info->var, &info->modelist);
|
|
|
|
|
|
|
|
if (mode) {
|
|
|
|
fb_videomode_to_var(&info->var, mode);
|
|
|
|
} else {
|
|
|
|
modeid = par->vbe_modes[0].mode_id;
|
2009-04-01 05:25:41 +07:00
|
|
|
uvesafb_setup_var(&info->var, info,
|
|
|
|
&par->vbe_modes[modeid]);
|
2007-10-16 15:28:26 +07:00
|
|
|
fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60,
|
2009-04-01 05:25:41 +07:00
|
|
|
&info->var, info);
|
|
|
|
|
2007-10-16 15:28:26 +07:00
|
|
|
goto gotmode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Look for a matching VBE mode. */
|
|
|
|
modeid = uvesafb_vbe_find_mode(par, info->var.xres, info->var.yres,
|
|
|
|
info->var.bits_per_pixel, UVESAFB_EXACT_RES);
|
|
|
|
|
|
|
|
if (modeid == -1)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
uvesafb_setup_var(&info->var, info, &par->vbe_modes[modeid]);
|
|
|
|
|
2009-04-01 05:25:41 +07:00
|
|
|
gotmode:
|
2007-10-16 15:28:26 +07:00
|
|
|
/*
|
|
|
|
* If we are not VBE3.0+ compliant, we're done -- the BIOS will
|
|
|
|
* ignore our timings anyway.
|
|
|
|
*/
|
|
|
|
if (par->vbe_ib.vbe_version < 0x0300 || par->nocrtc)
|
|
|
|
fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60,
|
|
|
|
&info->var, info);
|
|
|
|
|
|
|
|
return modeid;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_setpalette(struct uvesafb_pal_entry *entries, int count,
|
|
|
|
int start, struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct uvesafb_ktask *task;
|
2007-11-15 07:58:47 +07:00
|
|
|
#ifdef CONFIG_X86
|
2007-10-16 15:28:26 +07:00
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
int i = par->mode_idx;
|
2007-11-15 07:58:47 +07:00
|
|
|
#endif
|
2007-10-16 15:28:26 +07:00
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We support palette modifications for 8 bpp modes only, so
|
|
|
|
* there can never be more than 256 entries.
|
|
|
|
*/
|
|
|
|
if (start + count > 256)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
#ifdef CONFIG_X86
|
|
|
|
/* Use VGA registers if mode is VGA-compatible. */
|
|
|
|
if (i >= 0 && i < par->vbe_modes_cnt &&
|
|
|
|
par->vbe_modes[i].mode_attr & VBE_MODE_VGACOMPAT) {
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
outb_p(start + i, dac_reg);
|
|
|
|
outb_p(entries[i].red, dac_val);
|
|
|
|
outb_p(entries[i].green, dac_val);
|
|
|
|
outb_p(entries[i].blue, dac_val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef CONFIG_X86_32
|
|
|
|
else if (par->pmi_setpal) {
|
|
|
|
__asm__ __volatile__(
|
|
|
|
"call *(%%esi)"
|
|
|
|
: /* no return value */
|
|
|
|
: "a" (0x4f09), /* EAX */
|
|
|
|
"b" (0), /* EBX */
|
|
|
|
"c" (count), /* ECX */
|
|
|
|
"d" (start), /* EDX */
|
|
|
|
"D" (entries), /* EDI */
|
|
|
|
"S" (&par->pmi_pal)); /* ESI */
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_X86_32 */
|
|
|
|
else
|
|
|
|
#endif /* CONFIG_X86 */
|
|
|
|
{
|
|
|
|
task = uvesafb_prep();
|
|
|
|
if (!task)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
task->t.regs.eax = 0x4f09;
|
|
|
|
task->t.regs.ebx = 0x0;
|
|
|
|
task->t.regs.ecx = count;
|
|
|
|
task->t.regs.edx = start;
|
|
|
|
task->t.flags = TF_BUF_ESDI;
|
|
|
|
task->t.buf_len = sizeof(struct uvesafb_pal_entry) * count;
|
|
|
|
task->buf = entries;
|
|
|
|
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
if ((task->t.regs.eax & 0xffff) != 0x004f)
|
|
|
|
err = 1;
|
|
|
|
|
|
|
|
uvesafb_free(task);
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_setcolreg(unsigned regno, unsigned red, unsigned green,
|
|
|
|
unsigned blue, unsigned transp,
|
|
|
|
struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct uvesafb_pal_entry entry;
|
2009-04-14 04:39:43 +07:00
|
|
|
int shift = 16 - dac_width;
|
2007-10-16 15:28:26 +07:00
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
if (regno >= info->cmap.len)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (info->var.bits_per_pixel == 8) {
|
|
|
|
entry.red = red >> shift;
|
|
|
|
entry.green = green >> shift;
|
|
|
|
entry.blue = blue >> shift;
|
|
|
|
entry.pad = 0;
|
|
|
|
|
|
|
|
err = uvesafb_setpalette(&entry, 1, regno, info);
|
|
|
|
} else if (regno < 16) {
|
|
|
|
switch (info->var.bits_per_pixel) {
|
|
|
|
case 16:
|
|
|
|
if (info->var.red.offset == 10) {
|
|
|
|
/* 1:5:5:5 */
|
|
|
|
((u32 *) (info->pseudo_palette))[regno] =
|
|
|
|
((red & 0xf800) >> 1) |
|
|
|
|
((green & 0xf800) >> 6) |
|
|
|
|
((blue & 0xf800) >> 11);
|
|
|
|
} else {
|
|
|
|
/* 0:5:6:5 */
|
|
|
|
((u32 *) (info->pseudo_palette))[regno] =
|
|
|
|
((red & 0xf800) ) |
|
|
|
|
((green & 0xfc00) >> 5) |
|
|
|
|
((blue & 0xf800) >> 11);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 24:
|
|
|
|
case 32:
|
|
|
|
red >>= 8;
|
|
|
|
green >>= 8;
|
|
|
|
blue >>= 8;
|
|
|
|
((u32 *)(info->pseudo_palette))[regno] =
|
|
|
|
(red << info->var.red.offset) |
|
|
|
|
(green << info->var.green.offset) |
|
|
|
|
(blue << info->var.blue.offset);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct uvesafb_pal_entry *entries;
|
2009-04-14 04:39:43 +07:00
|
|
|
int shift = 16 - dac_width;
|
2007-10-16 15:28:26 +07:00
|
|
|
int i, err = 0;
|
|
|
|
|
|
|
|
if (info->var.bits_per_pixel == 8) {
|
|
|
|
if (cmap->start + cmap->len > info->cmap.start +
|
|
|
|
info->cmap.len || cmap->start < info->cmap.start)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
entries = kmalloc(sizeof(*entries) * cmap->len, GFP_KERNEL);
|
|
|
|
if (!entries)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
for (i = 0; i < cmap->len; i++) {
|
|
|
|
entries[i].red = cmap->red[i] >> shift;
|
|
|
|
entries[i].green = cmap->green[i] >> shift;
|
|
|
|
entries[i].blue = cmap->blue[i] >> shift;
|
|
|
|
entries[i].pad = 0;
|
|
|
|
}
|
|
|
|
err = uvesafb_setpalette(entries, cmap->len, cmap->start, info);
|
|
|
|
kfree(entries);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* For modes with bpp > 8, we only set the pseudo palette in
|
|
|
|
* the fb_info struct. We rely on uvesafb_setcolreg to do all
|
|
|
|
* sanity checking.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < cmap->len; i++) {
|
|
|
|
err |= uvesafb_setcolreg(cmap->start + i, cmap->red[i],
|
|
|
|
cmap->green[i], cmap->blue[i],
|
|
|
|
0, info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_pan_display(struct fb_var_screeninfo *var,
|
|
|
|
struct fb_info *info)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_X86_32
|
|
|
|
int offset;
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
offset = (var->yoffset * info->fix.line_length + var->xoffset) / 4;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* It turns out it's not the best idea to do panning via vm86,
|
|
|
|
* so we only allow it if we have a PMI.
|
|
|
|
*/
|
|
|
|
if (par->pmi_start) {
|
|
|
|
__asm__ __volatile__(
|
|
|
|
"call *(%%edi)"
|
|
|
|
: /* no return value */
|
|
|
|
: "a" (0x4f07), /* EAX */
|
|
|
|
"b" (0), /* EBX */
|
|
|
|
"c" (offset), /* ECX */
|
|
|
|
"d" (offset >> 16), /* EDX */
|
|
|
|
"D" (&par->pmi_start)); /* EDI */
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_blank(int blank, struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct uvesafb_ktask *task;
|
|
|
|
int err = 1;
|
|
|
|
#ifdef CONFIG_X86
|
2007-11-15 07:58:47 +07:00
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
2007-10-16 15:28:26 +07:00
|
|
|
if (par->vbe_ib.capabilities & VBE_CAP_VGACOMPAT) {
|
|
|
|
int loop = 10000;
|
|
|
|
u8 seq = 0, crtc17 = 0;
|
|
|
|
|
|
|
|
if (blank == FB_BLANK_POWERDOWN) {
|
|
|
|
seq = 0x20;
|
|
|
|
crtc17 = 0x00;
|
|
|
|
err = 0;
|
|
|
|
} else {
|
|
|
|
seq = 0x00;
|
|
|
|
crtc17 = 0x80;
|
|
|
|
err = (blank == FB_BLANK_UNBLANK) ? 0 : -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
vga_wseq(NULL, 0x00, 0x01);
|
|
|
|
seq |= vga_rseq(NULL, 0x01) & ~0x20;
|
|
|
|
vga_wseq(NULL, 0x00, seq);
|
|
|
|
|
|
|
|
crtc17 |= vga_rcrt(NULL, 0x17) & ~0x80;
|
|
|
|
while (loop--);
|
|
|
|
vga_wcrt(NULL, 0x17, crtc17);
|
|
|
|
vga_wseq(NULL, 0x00, 0x03);
|
|
|
|
} else
|
|
|
|
#endif /* CONFIG_X86 */
|
|
|
|
{
|
|
|
|
task = uvesafb_prep();
|
|
|
|
if (!task)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
task->t.regs.eax = 0x4f10;
|
|
|
|
switch (blank) {
|
|
|
|
case FB_BLANK_UNBLANK:
|
|
|
|
task->t.regs.ebx = 0x0001;
|
|
|
|
break;
|
|
|
|
case FB_BLANK_NORMAL:
|
|
|
|
task->t.regs.ebx = 0x0101; /* standby */
|
|
|
|
break;
|
|
|
|
case FB_BLANK_POWERDOWN:
|
|
|
|
task->t.regs.ebx = 0x0401; /* powerdown */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
if (err || (task->t.regs.eax & 0xffff) != 0x004f)
|
|
|
|
err = 1;
|
|
|
|
out: uvesafb_free(task);
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_open(struct fb_info *info, int user)
|
|
|
|
{
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
int cnt = atomic_read(&par->ref_count);
|
2012-03-02 08:45:31 +07:00
|
|
|
u8 *buf = NULL;
|
2007-10-16 15:28:26 +07:00
|
|
|
|
2012-03-02 08:45:31 +07:00
|
|
|
if (!cnt && par->vbe_state_size) {
|
|
|
|
buf = uvesafb_vbe_state_save(par);
|
|
|
|
if (IS_ERR(buf)) {
|
|
|
|
printk(KERN_WARNING "uvesafb: save hardware state"
|
|
|
|
"failed, error code is %ld!\n", PTR_ERR(buf));
|
|
|
|
} else {
|
|
|
|
par->vbe_state_orig = buf;
|
|
|
|
}
|
|
|
|
}
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
atomic_inc(&par->ref_count);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_release(struct fb_info *info, int user)
|
|
|
|
{
|
|
|
|
struct uvesafb_ktask *task = NULL;
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
int cnt = atomic_read(&par->ref_count);
|
|
|
|
|
|
|
|
if (!cnt)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (cnt != 1)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
task = uvesafb_prep();
|
|
|
|
if (!task)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/* First, try to set the standard 80x25 text mode. */
|
|
|
|
task->t.regs.eax = 0x0003;
|
|
|
|
uvesafb_exec(task);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now try to restore whatever hardware state we might have
|
|
|
|
* saved when the fb device was first opened.
|
|
|
|
*/
|
|
|
|
uvesafb_vbe_state_restore(par, par->vbe_state_orig);
|
|
|
|
out:
|
|
|
|
atomic_dec(&par->ref_count);
|
|
|
|
if (task)
|
|
|
|
uvesafb_free(task);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_set_par(struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
struct uvesafb_ktask *task = NULL;
|
|
|
|
struct vbe_crtc_ib *crtc = NULL;
|
|
|
|
struct vbe_mode_ib *mode = NULL;
|
|
|
|
int i, err = 0, depth = info->var.bits_per_pixel;
|
|
|
|
|
|
|
|
if (depth > 8 && depth != 32)
|
|
|
|
depth = info->var.red.length + info->var.green.length +
|
|
|
|
info->var.blue.length;
|
|
|
|
|
|
|
|
i = uvesafb_vbe_find_mode(par, info->var.xres, info->var.yres, depth,
|
|
|
|
UVESAFB_EXACT_RES | UVESAFB_EXACT_DEPTH);
|
|
|
|
if (i >= 0)
|
|
|
|
mode = &par->vbe_modes[i];
|
|
|
|
else
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
task = uvesafb_prep();
|
|
|
|
if (!task)
|
|
|
|
return -ENOMEM;
|
|
|
|
setmode:
|
|
|
|
task->t.regs.eax = 0x4f02;
|
|
|
|
task->t.regs.ebx = mode->mode_id | 0x4000; /* use LFB */
|
|
|
|
|
|
|
|
if (par->vbe_ib.vbe_version >= 0x0300 && !par->nocrtc &&
|
|
|
|
info->var.pixclock != 0) {
|
|
|
|
task->t.regs.ebx |= 0x0800; /* use CRTC data */
|
|
|
|
task->t.flags = TF_BUF_ESDI;
|
|
|
|
crtc = kzalloc(sizeof(struct vbe_crtc_ib), GFP_KERNEL);
|
|
|
|
if (!crtc) {
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
crtc->horiz_start = info->var.xres + info->var.right_margin;
|
|
|
|
crtc->horiz_end = crtc->horiz_start + info->var.hsync_len;
|
|
|
|
crtc->horiz_total = crtc->horiz_end + info->var.left_margin;
|
|
|
|
|
|
|
|
crtc->vert_start = info->var.yres + info->var.lower_margin;
|
|
|
|
crtc->vert_end = crtc->vert_start + info->var.vsync_len;
|
|
|
|
crtc->vert_total = crtc->vert_end + info->var.upper_margin;
|
|
|
|
|
|
|
|
crtc->pixel_clock = PICOS2KHZ(info->var.pixclock) * 1000;
|
|
|
|
crtc->refresh_rate = (u16)(100 * (crtc->pixel_clock /
|
|
|
|
(crtc->vert_total * crtc->horiz_total)));
|
|
|
|
|
|
|
|
if (info->var.vmode & FB_VMODE_DOUBLE)
|
|
|
|
crtc->flags |= 0x1;
|
|
|
|
if (info->var.vmode & FB_VMODE_INTERLACED)
|
|
|
|
crtc->flags |= 0x2;
|
|
|
|
if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT))
|
|
|
|
crtc->flags |= 0x4;
|
|
|
|
if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT))
|
|
|
|
crtc->flags |= 0x8;
|
|
|
|
memcpy(&par->crtc, crtc, sizeof(*crtc));
|
|
|
|
} else {
|
|
|
|
memset(&par->crtc, 0, sizeof(*crtc));
|
|
|
|
}
|
|
|
|
|
|
|
|
task->t.buf_len = sizeof(struct vbe_crtc_ib);
|
|
|
|
task->buf = &par->crtc;
|
|
|
|
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
if (err || (task->t.regs.eax & 0xffff) != 0x004f) {
|
|
|
|
/*
|
|
|
|
* The mode switch might have failed because we tried to
|
|
|
|
* use our own timings. Try again with the default timings.
|
|
|
|
*/
|
|
|
|
if (crtc != NULL) {
|
|
|
|
printk(KERN_WARNING "uvesafb: mode switch failed "
|
|
|
|
"(eax=0x%x, err=%d). Trying again with "
|
|
|
|
"default timings.\n", task->t.regs.eax, err);
|
|
|
|
uvesafb_reset(task);
|
|
|
|
kfree(crtc);
|
|
|
|
crtc = NULL;
|
|
|
|
info->var.pixclock = 0;
|
|
|
|
goto setmode;
|
|
|
|
} else {
|
|
|
|
printk(KERN_ERR "uvesafb: mode switch failed (eax="
|
|
|
|
"0x%x, err=%d)\n", task->t.regs.eax, err);
|
|
|
|
err = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
par->mode_idx = i;
|
|
|
|
|
|
|
|
/* For 8bpp modes, always try to set the DAC to 8 bits. */
|
|
|
|
if (par->vbe_ib.capabilities & VBE_CAP_CAN_SWITCH_DAC &&
|
|
|
|
mode->bits_per_pixel <= 8) {
|
|
|
|
uvesafb_reset(task);
|
|
|
|
task->t.regs.eax = 0x4f08;
|
|
|
|
task->t.regs.ebx = 0x0800;
|
|
|
|
|
|
|
|
err = uvesafb_exec(task);
|
|
|
|
if (err || (task->t.regs.eax & 0xffff) != 0x004f ||
|
|
|
|
((task->t.regs.ebx & 0xff00) >> 8) != 8) {
|
2009-04-14 04:39:43 +07:00
|
|
|
dac_width = 6;
|
|
|
|
} else {
|
|
|
|
dac_width = 8;
|
2007-10-16 15:28:26 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
info->fix.visual = (info->var.bits_per_pixel == 8) ?
|
|
|
|
FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
|
|
|
|
info->fix.line_length = mode->bytes_per_scan_line;
|
|
|
|
|
|
|
|
out: if (crtc != NULL)
|
|
|
|
kfree(crtc);
|
|
|
|
uvesafb_free(task);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void uvesafb_check_limits(struct fb_var_screeninfo *var,
|
|
|
|
struct fb_info *info)
|
|
|
|
{
|
|
|
|
const struct fb_videomode *mode;
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If pixclock is set to 0, then we're using default BIOS timings
|
|
|
|
* and thus don't have to perform any checks here.
|
|
|
|
*/
|
|
|
|
if (!var->pixclock)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (par->vbe_ib.vbe_version < 0x0300) {
|
|
|
|
fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60, var, info);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!fb_validate_mode(var, info))
|
|
|
|
return;
|
|
|
|
|
|
|
|
mode = fb_find_best_mode(var, &info->modelist);
|
|
|
|
if (mode) {
|
|
|
|
if (mode->xres == var->xres && mode->yres == var->yres &&
|
|
|
|
!(mode->vmode & (FB_VMODE_INTERLACED | FB_VMODE_DOUBLE))) {
|
|
|
|
fb_videomode_to_var(var, mode);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->monspecs.gtf && !fb_get_mode(FB_MAXTIMINGS, 0, var, info))
|
|
|
|
return;
|
|
|
|
/* Use default refresh rate */
|
|
|
|
var->pixclock = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_check_var(struct fb_var_screeninfo *var,
|
|
|
|
struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
struct vbe_mode_ib *mode = NULL;
|
|
|
|
int match = -1;
|
|
|
|
int depth = var->red.length + var->green.length + var->blue.length;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Various apps will use bits_per_pixel to set the color depth,
|
|
|
|
* which is theoretically incorrect, but which we'll try to handle
|
|
|
|
* here.
|
|
|
|
*/
|
|
|
|
if (depth == 0 || abs(depth - var->bits_per_pixel) >= 8)
|
|
|
|
depth = var->bits_per_pixel;
|
|
|
|
|
|
|
|
match = uvesafb_vbe_find_mode(par, var->xres, var->yres, depth,
|
|
|
|
UVESAFB_EXACT_RES);
|
|
|
|
if (match == -1)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
mode = &par->vbe_modes[match];
|
|
|
|
uvesafb_setup_var(var, info, mode);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check whether we have remapped enough memory for this mode.
|
|
|
|
* We might be called at an early stage, when we haven't remapped
|
|
|
|
* any memory yet, in which case we simply skip the check.
|
|
|
|
*/
|
|
|
|
if (var->yres * mode->bytes_per_scan_line > info->fix.smem_len
|
|
|
|
&& info->fix.smem_len)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if ((var->vmode & FB_VMODE_DOUBLE) &&
|
|
|
|
!(par->vbe_modes[match].mode_attr & 0x100))
|
|
|
|
var->vmode &= ~FB_VMODE_DOUBLE;
|
|
|
|
|
|
|
|
if ((var->vmode & FB_VMODE_INTERLACED) &&
|
|
|
|
!(par->vbe_modes[match].mode_attr & 0x200))
|
|
|
|
var->vmode &= ~FB_VMODE_INTERLACED;
|
|
|
|
|
|
|
|
uvesafb_check_limits(var, info);
|
|
|
|
|
|
|
|
var->xres_virtual = var->xres;
|
|
|
|
var->yres_virtual = (par->ypan) ?
|
|
|
|
info->fix.smem_len / mode->bytes_per_scan_line :
|
|
|
|
var->yres;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct fb_ops uvesafb_ops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.fb_open = uvesafb_open,
|
|
|
|
.fb_release = uvesafb_release,
|
|
|
|
.fb_setcolreg = uvesafb_setcolreg,
|
|
|
|
.fb_setcmap = uvesafb_setcmap,
|
|
|
|
.fb_pan_display = uvesafb_pan_display,
|
|
|
|
.fb_blank = uvesafb_blank,
|
|
|
|
.fb_fillrect = cfb_fillrect,
|
|
|
|
.fb_copyarea = cfb_copyarea,
|
|
|
|
.fb_imageblit = cfb_imageblit,
|
|
|
|
.fb_check_var = uvesafb_check_var,
|
|
|
|
.fb_set_par = uvesafb_set_par,
|
|
|
|
};
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static void uvesafb_init_info(struct fb_info *info, struct vbe_mode_ib *mode)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
unsigned int size_vmode;
|
|
|
|
unsigned int size_remap;
|
|
|
|
unsigned int size_total;
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
int i, h;
|
|
|
|
|
|
|
|
info->pseudo_palette = ((u8 *)info->par + sizeof(struct uvesafb_par));
|
|
|
|
info->fix = uvesafb_fix;
|
|
|
|
info->fix.ypanstep = par->ypan ? 1 : 0;
|
|
|
|
info->fix.ywrapstep = (par->ypan > 1) ? 1 : 0;
|
|
|
|
|
|
|
|
/* Disable blanking if the user requested so. */
|
|
|
|
if (!blank)
|
|
|
|
info->fbops->fb_blank = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find out how much IO memory is required for the mode with
|
|
|
|
* the highest resolution.
|
|
|
|
*/
|
|
|
|
size_remap = 0;
|
|
|
|
for (i = 0; i < par->vbe_modes_cnt; i++) {
|
|
|
|
h = par->vbe_modes[i].bytes_per_scan_line *
|
|
|
|
par->vbe_modes[i].y_res;
|
|
|
|
if (h > size_remap)
|
|
|
|
size_remap = h;
|
|
|
|
}
|
|
|
|
size_remap *= 2;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* size_vmode -- that is the amount of memory needed for the
|
|
|
|
* used video mode, i.e. the minimum amount of
|
|
|
|
* memory we need.
|
|
|
|
*/
|
|
|
|
if (mode != NULL) {
|
|
|
|
size_vmode = info->var.yres * mode->bytes_per_scan_line;
|
|
|
|
} else {
|
|
|
|
size_vmode = info->var.yres * info->var.xres *
|
|
|
|
((info->var.bits_per_pixel + 7) >> 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* size_total -- all video memory we have. Used for mtrr
|
|
|
|
* entries, resource allocation and bounds
|
|
|
|
* checking.
|
|
|
|
*/
|
|
|
|
size_total = par->vbe_ib.total_memory * 65536;
|
|
|
|
if (vram_total)
|
|
|
|
size_total = vram_total * 1024 * 1024;
|
|
|
|
if (size_total < size_vmode)
|
|
|
|
size_total = size_vmode;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* size_remap -- the amount of video memory we are going to
|
|
|
|
* use for vesafb. With modern cards it is no
|
|
|
|
* option to simply use size_total as th
|
|
|
|
* wastes plenty of kernel address space.
|
|
|
|
*/
|
|
|
|
if (vram_remap)
|
|
|
|
size_remap = vram_remap * 1024 * 1024;
|
|
|
|
if (size_remap < size_vmode)
|
|
|
|
size_remap = size_vmode;
|
|
|
|
if (size_remap > size_total)
|
|
|
|
size_remap = size_total;
|
|
|
|
|
|
|
|
info->fix.smem_len = size_remap;
|
|
|
|
info->fix.smem_start = mode->phys_base_ptr;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We have to set yres_virtual here because when setup_var() was
|
|
|
|
* called, smem_len wasn't defined yet.
|
|
|
|
*/
|
|
|
|
info->var.yres_virtual = info->fix.smem_len /
|
|
|
|
mode->bytes_per_scan_line;
|
|
|
|
|
|
|
|
if (par->ypan && info->var.yres_virtual > info->var.yres) {
|
|
|
|
printk(KERN_INFO "uvesafb: scrolling: %s "
|
|
|
|
"using protected mode interface, "
|
|
|
|
"yres_virtual=%d\n",
|
|
|
|
(par->ypan > 1) ? "ywrap" : "ypan",
|
|
|
|
info->var.yres_virtual);
|
|
|
|
} else {
|
|
|
|
printk(KERN_INFO "uvesafb: scrolling: redraw\n");
|
|
|
|
info->var.yres_virtual = info->var.yres;
|
|
|
|
par->ypan = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
info->flags = FBINFO_FLAG_DEFAULT |
|
2009-04-01 05:25:35 +07:00
|
|
|
(par->ypan ? FBINFO_HWACCEL_YPAN : 0);
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
if (!par->ypan)
|
|
|
|
info->fbops->fb_pan_display = NULL;
|
|
|
|
}
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static void uvesafb_init_mtrr(struct fb_info *info)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
#ifdef CONFIG_MTRR
|
|
|
|
if (mtrr && !(info->fix.smem_start & (PAGE_SIZE - 1))) {
|
|
|
|
int temp_size = info->fix.smem_len;
|
|
|
|
unsigned int type = 0;
|
|
|
|
|
|
|
|
switch (mtrr) {
|
|
|
|
case 1:
|
|
|
|
type = MTRR_TYPE_UNCACHABLE;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
type = MTRR_TYPE_WRBACK;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
type = MTRR_TYPE_WRCOMB;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
type = MTRR_TYPE_WRTHROUGH;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
type = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type) {
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* Find the largest power-of-two */
|
uvesafb,vesafb: create WC or WB PAT-entries
with an PAT-enabled kernel, when using uvesafb or vesafb, these drivers will
create uncached-minus PAT entries for the framebuffer memory because they use
ioremap() (not the *_cache or *_wc variants). When the framebuffer memory
intersects with the video RAM used by Xorg, the complete video RAM will be
mapped uncached-minus what results in a serve performance penalty.
Here are the correct MTRR entries created by uvesafb:
schlicht@netbook:~$ cat /proc/mtrr
reg00: base=0x000000000 ( 0MB), size= 2048MB, count=1: write-back
reg01: base=0x06ff00000 ( 1791MB), size= 1MB, count=1: uncachable
reg02: base=0x070000000 ( 1792MB), size= 256MB, count=1: uncachable
reg03: base=0x0d0000000 ( 3328MB), size= 16MB, count=1: write-combining
And here are the problematic PAT entries:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0xd0000000-0xe0000000 <-- created by xserver-xorg
uncached-minus @ 0xd0000000-0xd1194000 <-- created by uvesafb
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
Therefore I created the attached patch for uvesafb which uses ioremap_wc() to
create the correct PAT entries, as shown below:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
write-combining @ 0xd0000000-0xe0000000
write-combining @ 0xd0000000-0xd1194000
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
This results in a performance gain, objectively measurable with e.g.
x11perf -comppixwin10 -comppixwin100 -comppixwin500:
1: x11perf_xaa.log
2: x11perf_xaa_patched.log
1 2 Operation
-------- ---------------- -----------------
124000.0 202000.0 ( 1.63) Composite 10x10 from pixmap to window
3340.0 24400.0 ( 7.31) Composite 100x100 from pixmap to window
131.0 1150.0 ( 8.78) Composite 500x500 from pixmap to window
You can see the serve performance gain when composing larger pixmaps to window.
The patches replace the ioremap() function with the variant matching the mtrr-
parameter. To create "write-back" PAT entries, the ioremap_cache() function
must be called after creating the MTRR entries, and the ioremap_cache() region
must completely fit into the MTRR region, this is why the MTRR region size is
now rounded up to the next power-of-two.
Signed-off-by: Thomas Schlichter <thomas.schlichter@web.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
2010-11-27 20:17:55 +07:00
|
|
|
temp_size = roundup_pow_of_two(temp_size);
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
/* Try and find a power of two to add */
|
|
|
|
do {
|
|
|
|
rc = mtrr_add(info->fix.smem_start,
|
|
|
|
temp_size, type, 1);
|
|
|
|
temp_size >>= 1;
|
|
|
|
} while (temp_size >= PAGE_SIZE && rc == -EINVAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_MTRR */
|
|
|
|
}
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static void uvesafb_ioremap(struct fb_info *info)
|
uvesafb,vesafb: create WC or WB PAT-entries
with an PAT-enabled kernel, when using uvesafb or vesafb, these drivers will
create uncached-minus PAT entries for the framebuffer memory because they use
ioremap() (not the *_cache or *_wc variants). When the framebuffer memory
intersects with the video RAM used by Xorg, the complete video RAM will be
mapped uncached-minus what results in a serve performance penalty.
Here are the correct MTRR entries created by uvesafb:
schlicht@netbook:~$ cat /proc/mtrr
reg00: base=0x000000000 ( 0MB), size= 2048MB, count=1: write-back
reg01: base=0x06ff00000 ( 1791MB), size= 1MB, count=1: uncachable
reg02: base=0x070000000 ( 1792MB), size= 256MB, count=1: uncachable
reg03: base=0x0d0000000 ( 3328MB), size= 16MB, count=1: write-combining
And here are the problematic PAT entries:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0xd0000000-0xe0000000 <-- created by xserver-xorg
uncached-minus @ 0xd0000000-0xd1194000 <-- created by uvesafb
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
Therefore I created the attached patch for uvesafb which uses ioremap_wc() to
create the correct PAT entries, as shown below:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
write-combining @ 0xd0000000-0xe0000000
write-combining @ 0xd0000000-0xd1194000
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
This results in a performance gain, objectively measurable with e.g.
x11perf -comppixwin10 -comppixwin100 -comppixwin500:
1: x11perf_xaa.log
2: x11perf_xaa_patched.log
1 2 Operation
-------- ---------------- -----------------
124000.0 202000.0 ( 1.63) Composite 10x10 from pixmap to window
3340.0 24400.0 ( 7.31) Composite 100x100 from pixmap to window
131.0 1150.0 ( 8.78) Composite 500x500 from pixmap to window
You can see the serve performance gain when composing larger pixmaps to window.
The patches replace the ioremap() function with the variant matching the mtrr-
parameter. To create "write-back" PAT entries, the ioremap_cache() function
must be called after creating the MTRR entries, and the ioremap_cache() region
must completely fit into the MTRR region, this is why the MTRR region size is
now rounded up to the next power-of-two.
Signed-off-by: Thomas Schlichter <thomas.schlichter@web.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
2010-11-27 20:17:55 +07:00
|
|
|
{
|
|
|
|
#ifdef CONFIG_X86
|
|
|
|
switch (mtrr) {
|
|
|
|
case 1: /* uncachable */
|
|
|
|
info->screen_base = ioremap_nocache(info->fix.smem_start, info->fix.smem_len);
|
|
|
|
break;
|
|
|
|
case 2: /* write-back */
|
|
|
|
info->screen_base = ioremap_cache(info->fix.smem_start, info->fix.smem_len);
|
|
|
|
break;
|
|
|
|
case 3: /* write-combining */
|
|
|
|
info->screen_base = ioremap_wc(info->fix.smem_start, info->fix.smem_len);
|
|
|
|
break;
|
|
|
|
case 4: /* write-through */
|
|
|
|
default:
|
|
|
|
info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len);
|
|
|
|
#endif /* CONFIG_X86 */
|
|
|
|
}
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
static ssize_t uvesafb_show_vbe_ver(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *info = platform_get_drvdata(to_platform_device(dev));
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%.4x\n", par->vbe_ib.vbe_version);
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(vbe_version, S_IRUGO, uvesafb_show_vbe_ver, NULL);
|
|
|
|
|
|
|
|
static ssize_t uvesafb_show_vbe_modes(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *info = platform_get_drvdata(to_platform_device(dev));
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
int ret = 0, i;
|
|
|
|
|
|
|
|
for (i = 0; i < par->vbe_modes_cnt && ret < PAGE_SIZE; i++) {
|
|
|
|
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
|
|
|
"%dx%d-%d, 0x%.4x\n",
|
|
|
|
par->vbe_modes[i].x_res, par->vbe_modes[i].y_res,
|
|
|
|
par->vbe_modes[i].depth, par->vbe_modes[i].mode_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(vbe_modes, S_IRUGO, uvesafb_show_vbe_modes, NULL);
|
|
|
|
|
|
|
|
static ssize_t uvesafb_show_vendor(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *info = platform_get_drvdata(to_platform_device(dev));
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
if (par->vbe_ib.oem_vendor_name_ptr)
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", (char *)
|
|
|
|
(&par->vbe_ib) + par->vbe_ib.oem_vendor_name_ptr);
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(oem_vendor, S_IRUGO, uvesafb_show_vendor, NULL);
|
|
|
|
|
|
|
|
static ssize_t uvesafb_show_product_name(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *info = platform_get_drvdata(to_platform_device(dev));
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
if (par->vbe_ib.oem_product_name_ptr)
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", (char *)
|
|
|
|
(&par->vbe_ib) + par->vbe_ib.oem_product_name_ptr);
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(oem_product_name, S_IRUGO, uvesafb_show_product_name, NULL);
|
|
|
|
|
|
|
|
static ssize_t uvesafb_show_product_rev(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *info = platform_get_drvdata(to_platform_device(dev));
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
if (par->vbe_ib.oem_product_rev_ptr)
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", (char *)
|
|
|
|
(&par->vbe_ib) + par->vbe_ib.oem_product_rev_ptr);
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(oem_product_rev, S_IRUGO, uvesafb_show_product_rev, NULL);
|
|
|
|
|
|
|
|
static ssize_t uvesafb_show_oem_string(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *info = platform_get_drvdata(to_platform_device(dev));
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
if (par->vbe_ib.oem_string_ptr)
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",
|
|
|
|
(char *)(&par->vbe_ib) + par->vbe_ib.oem_string_ptr);
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(oem_string, S_IRUGO, uvesafb_show_oem_string, NULL);
|
|
|
|
|
|
|
|
static ssize_t uvesafb_show_nocrtc(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *info = platform_get_drvdata(to_platform_device(dev));
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", par->nocrtc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t uvesafb_store_nocrtc(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct fb_info *info = platform_get_drvdata(to_platform_device(dev));
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
if (count > 0) {
|
|
|
|
if (buf[0] == '0')
|
|
|
|
par->nocrtc = 0;
|
|
|
|
else
|
|
|
|
par->nocrtc = 1;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(nocrtc, S_IRUGO | S_IWUSR, uvesafb_show_nocrtc,
|
|
|
|
uvesafb_store_nocrtc);
|
|
|
|
|
|
|
|
static struct attribute *uvesafb_dev_attrs[] = {
|
|
|
|
&dev_attr_vbe_version.attr,
|
|
|
|
&dev_attr_vbe_modes.attr,
|
|
|
|
&dev_attr_oem_vendor.attr,
|
|
|
|
&dev_attr_oem_product_name.attr,
|
|
|
|
&dev_attr_oem_product_rev.attr,
|
|
|
|
&dev_attr_oem_string.attr,
|
|
|
|
&dev_attr_nocrtc.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct attribute_group uvesafb_dev_attgrp = {
|
|
|
|
.name = NULL,
|
|
|
|
.attrs = uvesafb_dev_attrs,
|
|
|
|
};
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static int uvesafb_probe(struct platform_device *dev)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
struct fb_info *info;
|
|
|
|
struct vbe_mode_ib *mode = NULL;
|
|
|
|
struct uvesafb_par *par;
|
|
|
|
int err = 0, i;
|
|
|
|
|
|
|
|
info = framebuffer_alloc(sizeof(*par) + sizeof(u32) * 256, &dev->dev);
|
|
|
|
if (!info)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
par = info->par;
|
|
|
|
|
|
|
|
err = uvesafb_vbe_init(info);
|
|
|
|
if (err) {
|
|
|
|
printk(KERN_ERR "uvesafb: vbe_init() failed with %d\n", err);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
info->fbops = &uvesafb_ops;
|
|
|
|
|
|
|
|
i = uvesafb_vbe_init_mode(info);
|
|
|
|
if (i < 0) {
|
|
|
|
err = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
} else {
|
|
|
|
mode = &par->vbe_modes[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) {
|
|
|
|
err = -ENXIO;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
uvesafb_init_info(info, mode);
|
|
|
|
|
uvesafb,vesafb: create WC or WB PAT-entries
with an PAT-enabled kernel, when using uvesafb or vesafb, these drivers will
create uncached-minus PAT entries for the framebuffer memory because they use
ioremap() (not the *_cache or *_wc variants). When the framebuffer memory
intersects with the video RAM used by Xorg, the complete video RAM will be
mapped uncached-minus what results in a serve performance penalty.
Here are the correct MTRR entries created by uvesafb:
schlicht@netbook:~$ cat /proc/mtrr
reg00: base=0x000000000 ( 0MB), size= 2048MB, count=1: write-back
reg01: base=0x06ff00000 ( 1791MB), size= 1MB, count=1: uncachable
reg02: base=0x070000000 ( 1792MB), size= 256MB, count=1: uncachable
reg03: base=0x0d0000000 ( 3328MB), size= 16MB, count=1: write-combining
And here are the problematic PAT entries:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0xd0000000-0xe0000000 <-- created by xserver-xorg
uncached-minus @ 0xd0000000-0xd1194000 <-- created by uvesafb
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
Therefore I created the attached patch for uvesafb which uses ioremap_wc() to
create the correct PAT entries, as shown below:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
write-combining @ 0xd0000000-0xe0000000
write-combining @ 0xd0000000-0xd1194000
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
This results in a performance gain, objectively measurable with e.g.
x11perf -comppixwin10 -comppixwin100 -comppixwin500:
1: x11perf_xaa.log
2: x11perf_xaa_patched.log
1 2 Operation
-------- ---------------- -----------------
124000.0 202000.0 ( 1.63) Composite 10x10 from pixmap to window
3340.0 24400.0 ( 7.31) Composite 100x100 from pixmap to window
131.0 1150.0 ( 8.78) Composite 500x500 from pixmap to window
You can see the serve performance gain when composing larger pixmaps to window.
The patches replace the ioremap() function with the variant matching the mtrr-
parameter. To create "write-back" PAT entries, the ioremap_cache() function
must be called after creating the MTRR entries, and the ioremap_cache() region
must completely fit into the MTRR region, this is why the MTRR region size is
now rounded up to the next power-of-two.
Signed-off-by: Thomas Schlichter <thomas.schlichter@web.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
2010-11-27 20:17:55 +07:00
|
|
|
if (!request_region(0x3c0, 32, "uvesafb")) {
|
|
|
|
printk(KERN_ERR "uvesafb: request region 0x3c0-0x3e0 failed\n");
|
|
|
|
err = -EIO;
|
|
|
|
goto out_mode;
|
|
|
|
}
|
|
|
|
|
2007-10-16 15:28:26 +07:00
|
|
|
if (!request_mem_region(info->fix.smem_start, info->fix.smem_len,
|
|
|
|
"uvesafb")) {
|
|
|
|
printk(KERN_ERR "uvesafb: cannot reserve video memory at "
|
|
|
|
"0x%lx\n", info->fix.smem_start);
|
|
|
|
err = -EIO;
|
uvesafb,vesafb: create WC or WB PAT-entries
with an PAT-enabled kernel, when using uvesafb or vesafb, these drivers will
create uncached-minus PAT entries for the framebuffer memory because they use
ioremap() (not the *_cache or *_wc variants). When the framebuffer memory
intersects with the video RAM used by Xorg, the complete video RAM will be
mapped uncached-minus what results in a serve performance penalty.
Here are the correct MTRR entries created by uvesafb:
schlicht@netbook:~$ cat /proc/mtrr
reg00: base=0x000000000 ( 0MB), size= 2048MB, count=1: write-back
reg01: base=0x06ff00000 ( 1791MB), size= 1MB, count=1: uncachable
reg02: base=0x070000000 ( 1792MB), size= 256MB, count=1: uncachable
reg03: base=0x0d0000000 ( 3328MB), size= 16MB, count=1: write-combining
And here are the problematic PAT entries:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0xd0000000-0xe0000000 <-- created by xserver-xorg
uncached-minus @ 0xd0000000-0xd1194000 <-- created by uvesafb
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
Therefore I created the attached patch for uvesafb which uses ioremap_wc() to
create the correct PAT entries, as shown below:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
write-combining @ 0xd0000000-0xe0000000
write-combining @ 0xd0000000-0xd1194000
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
This results in a performance gain, objectively measurable with e.g.
x11perf -comppixwin10 -comppixwin100 -comppixwin500:
1: x11perf_xaa.log
2: x11perf_xaa_patched.log
1 2 Operation
-------- ---------------- -----------------
124000.0 202000.0 ( 1.63) Composite 10x10 from pixmap to window
3340.0 24400.0 ( 7.31) Composite 100x100 from pixmap to window
131.0 1150.0 ( 8.78) Composite 500x500 from pixmap to window
You can see the serve performance gain when composing larger pixmaps to window.
The patches replace the ioremap() function with the variant matching the mtrr-
parameter. To create "write-back" PAT entries, the ioremap_cache() function
must be called after creating the MTRR entries, and the ioremap_cache() region
must completely fit into the MTRR region, this is why the MTRR region size is
now rounded up to the next power-of-two.
Signed-off-by: Thomas Schlichter <thomas.schlichter@web.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
2010-11-27 20:17:55 +07:00
|
|
|
goto out_reg;
|
2007-10-16 15:28:26 +07:00
|
|
|
}
|
|
|
|
|
uvesafb,vesafb: create WC or WB PAT-entries
with an PAT-enabled kernel, when using uvesafb or vesafb, these drivers will
create uncached-minus PAT entries for the framebuffer memory because they use
ioremap() (not the *_cache or *_wc variants). When the framebuffer memory
intersects with the video RAM used by Xorg, the complete video RAM will be
mapped uncached-minus what results in a serve performance penalty.
Here are the correct MTRR entries created by uvesafb:
schlicht@netbook:~$ cat /proc/mtrr
reg00: base=0x000000000 ( 0MB), size= 2048MB, count=1: write-back
reg01: base=0x06ff00000 ( 1791MB), size= 1MB, count=1: uncachable
reg02: base=0x070000000 ( 1792MB), size= 256MB, count=1: uncachable
reg03: base=0x0d0000000 ( 3328MB), size= 16MB, count=1: write-combining
And here are the problematic PAT entries:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0xd0000000-0xe0000000 <-- created by xserver-xorg
uncached-minus @ 0xd0000000-0xd1194000 <-- created by uvesafb
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
Therefore I created the attached patch for uvesafb which uses ioremap_wc() to
create the correct PAT entries, as shown below:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
write-combining @ 0xd0000000-0xe0000000
write-combining @ 0xd0000000-0xd1194000
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
This results in a performance gain, objectively measurable with e.g.
x11perf -comppixwin10 -comppixwin100 -comppixwin500:
1: x11perf_xaa.log
2: x11perf_xaa_patched.log
1 2 Operation
-------- ---------------- -----------------
124000.0 202000.0 ( 1.63) Composite 10x10 from pixmap to window
3340.0 24400.0 ( 7.31) Composite 100x100 from pixmap to window
131.0 1150.0 ( 8.78) Composite 500x500 from pixmap to window
You can see the serve performance gain when composing larger pixmaps to window.
The patches replace the ioremap() function with the variant matching the mtrr-
parameter. To create "write-back" PAT entries, the ioremap_cache() function
must be called after creating the MTRR entries, and the ioremap_cache() region
must completely fit into the MTRR region, this is why the MTRR region size is
now rounded up to the next power-of-two.
Signed-off-by: Thomas Schlichter <thomas.schlichter@web.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
2010-11-27 20:17:55 +07:00
|
|
|
uvesafb_init_mtrr(info);
|
|
|
|
uvesafb_ioremap(info);
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
if (!info->screen_base) {
|
|
|
|
printk(KERN_ERR
|
|
|
|
"uvesafb: abort, cannot ioremap 0x%x bytes of video "
|
|
|
|
"memory at 0x%lx\n",
|
|
|
|
info->fix.smem_len, info->fix.smem_start);
|
|
|
|
err = -EIO;
|
|
|
|
goto out_mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
platform_set_drvdata(dev, info);
|
|
|
|
|
|
|
|
if (register_framebuffer(info) < 0) {
|
|
|
|
printk(KERN_ERR
|
|
|
|
"uvesafb: failed to register framebuffer device\n");
|
|
|
|
err = -EINVAL;
|
uvesafb,vesafb: create WC or WB PAT-entries
with an PAT-enabled kernel, when using uvesafb or vesafb, these drivers will
create uncached-minus PAT entries for the framebuffer memory because they use
ioremap() (not the *_cache or *_wc variants). When the framebuffer memory
intersects with the video RAM used by Xorg, the complete video RAM will be
mapped uncached-minus what results in a serve performance penalty.
Here are the correct MTRR entries created by uvesafb:
schlicht@netbook:~$ cat /proc/mtrr
reg00: base=0x000000000 ( 0MB), size= 2048MB, count=1: write-back
reg01: base=0x06ff00000 ( 1791MB), size= 1MB, count=1: uncachable
reg02: base=0x070000000 ( 1792MB), size= 256MB, count=1: uncachable
reg03: base=0x0d0000000 ( 3328MB), size= 16MB, count=1: write-combining
And here are the problematic PAT entries:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0xd0000000-0xe0000000 <-- created by xserver-xorg
uncached-minus @ 0xd0000000-0xd1194000 <-- created by uvesafb
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
Therefore I created the attached patch for uvesafb which uses ioremap_wc() to
create the correct PAT entries, as shown below:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
write-combining @ 0xd0000000-0xe0000000
write-combining @ 0xd0000000-0xd1194000
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
This results in a performance gain, objectively measurable with e.g.
x11perf -comppixwin10 -comppixwin100 -comppixwin500:
1: x11perf_xaa.log
2: x11perf_xaa_patched.log
1 2 Operation
-------- ---------------- -----------------
124000.0 202000.0 ( 1.63) Composite 10x10 from pixmap to window
3340.0 24400.0 ( 7.31) Composite 100x100 from pixmap to window
131.0 1150.0 ( 8.78) Composite 500x500 from pixmap to window
You can see the serve performance gain when composing larger pixmaps to window.
The patches replace the ioremap() function with the variant matching the mtrr-
parameter. To create "write-back" PAT entries, the ioremap_cache() function
must be called after creating the MTRR entries, and the ioremap_cache() region
must completely fit into the MTRR region, this is why the MTRR region size is
now rounded up to the next power-of-two.
Signed-off-by: Thomas Schlichter <thomas.schlichter@web.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
2010-11-27 20:17:55 +07:00
|
|
|
goto out_unmap;
|
2007-10-16 15:28:26 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
printk(KERN_INFO "uvesafb: framebuffer at 0x%lx, mapped to 0x%p, "
|
|
|
|
"using %dk, total %dk\n", info->fix.smem_start,
|
|
|
|
info->screen_base, info->fix.smem_len/1024,
|
|
|
|
par->vbe_ib.total_memory * 64);
|
|
|
|
printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
|
|
|
|
info->fix.id);
|
|
|
|
|
|
|
|
err = sysfs_create_group(&dev->dev.kobj, &uvesafb_dev_attgrp);
|
|
|
|
if (err != 0)
|
|
|
|
printk(KERN_WARNING "fb%d: failed to register attributes\n",
|
|
|
|
info->node);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_unmap:
|
|
|
|
iounmap(info->screen_base);
|
|
|
|
out_mem:
|
|
|
|
release_mem_region(info->fix.smem_start, info->fix.smem_len);
|
uvesafb,vesafb: create WC or WB PAT-entries
with an PAT-enabled kernel, when using uvesafb or vesafb, these drivers will
create uncached-minus PAT entries for the framebuffer memory because they use
ioremap() (not the *_cache or *_wc variants). When the framebuffer memory
intersects with the video RAM used by Xorg, the complete video RAM will be
mapped uncached-minus what results in a serve performance penalty.
Here are the correct MTRR entries created by uvesafb:
schlicht@netbook:~$ cat /proc/mtrr
reg00: base=0x000000000 ( 0MB), size= 2048MB, count=1: write-back
reg01: base=0x06ff00000 ( 1791MB), size= 1MB, count=1: uncachable
reg02: base=0x070000000 ( 1792MB), size= 256MB, count=1: uncachable
reg03: base=0x0d0000000 ( 3328MB), size= 16MB, count=1: write-combining
And here are the problematic PAT entries:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0xd0000000-0xe0000000 <-- created by xserver-xorg
uncached-minus @ 0xd0000000-0xd1194000 <-- created by uvesafb
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
Therefore I created the attached patch for uvesafb which uses ioremap_wc() to
create the correct PAT entries, as shown below:
schlicht@netbook:~$ sudo cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
write-back @ 0x0-0x1000
uncached-minus @ 0x6fedd000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee2000-0x6fee3000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
uncached-minus @ 0x6fee3000-0x6fee4000
write-combining @ 0xd0000000-0xe0000000
write-combining @ 0xd0000000-0xd1194000
uncached-minus @ 0xf4000000-0xf4009000
uncached-minus @ 0xf4200000-0xf4400000
uncached-minus @ 0xf5000000-0xf5010000
uncached-minus @ 0xf5100000-0xf5104000
uncached-minus @ 0xf5400000-0xf5404000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xf5404000-0xf5405000
uncached-minus @ 0xfed00000-0xfed01000
This results in a performance gain, objectively measurable with e.g.
x11perf -comppixwin10 -comppixwin100 -comppixwin500:
1: x11perf_xaa.log
2: x11perf_xaa_patched.log
1 2 Operation
-------- ---------------- -----------------
124000.0 202000.0 ( 1.63) Composite 10x10 from pixmap to window
3340.0 24400.0 ( 7.31) Composite 100x100 from pixmap to window
131.0 1150.0 ( 8.78) Composite 500x500 from pixmap to window
You can see the serve performance gain when composing larger pixmaps to window.
The patches replace the ioremap() function with the variant matching the mtrr-
parameter. To create "write-back" PAT entries, the ioremap_cache() function
must be called after creating the MTRR entries, and the ioremap_cache() region
must completely fit into the MTRR region, this is why the MTRR region size is
now rounded up to the next power-of-two.
Signed-off-by: Thomas Schlichter <thomas.schlichter@web.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
2010-11-27 20:17:55 +07:00
|
|
|
out_reg:
|
|
|
|
release_region(0x3c0, 32);
|
2007-10-16 15:28:26 +07:00
|
|
|
out_mode:
|
|
|
|
if (!list_empty(&info->modelist))
|
|
|
|
fb_destroy_modelist(&info->modelist);
|
|
|
|
fb_destroy_modedb(info->monspecs.modedb);
|
|
|
|
fb_dealloc_cmap(&info->cmap);
|
|
|
|
out:
|
|
|
|
if (par->vbe_modes)
|
|
|
|
kfree(par->vbe_modes);
|
|
|
|
|
|
|
|
framebuffer_release(info);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int uvesafb_remove(struct platform_device *dev)
|
|
|
|
{
|
|
|
|
struct fb_info *info = platform_get_drvdata(dev);
|
|
|
|
|
|
|
|
if (info) {
|
|
|
|
struct uvesafb_par *par = info->par;
|
|
|
|
|
|
|
|
sysfs_remove_group(&dev->dev.kobj, &uvesafb_dev_attgrp);
|
|
|
|
unregister_framebuffer(info);
|
|
|
|
release_region(0x3c0, 32);
|
|
|
|
iounmap(info->screen_base);
|
|
|
|
release_mem_region(info->fix.smem_start, info->fix.smem_len);
|
|
|
|
fb_destroy_modedb(info->monspecs.modedb);
|
|
|
|
fb_dealloc_cmap(&info->cmap);
|
|
|
|
|
|
|
|
if (par) {
|
|
|
|
if (par->vbe_modes)
|
|
|
|
kfree(par->vbe_modes);
|
|
|
|
if (par->vbe_state_orig)
|
|
|
|
kfree(par->vbe_state_orig);
|
|
|
|
if (par->vbe_state_saved)
|
|
|
|
kfree(par->vbe_state_saved);
|
|
|
|
}
|
|
|
|
|
|
|
|
framebuffer_release(info);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct platform_driver uvesafb_driver = {
|
|
|
|
.probe = uvesafb_probe,
|
|
|
|
.remove = uvesafb_remove,
|
|
|
|
.driver = {
|
|
|
|
.name = "uvesafb",
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct platform_device *uvesafb_device;
|
|
|
|
|
|
|
|
#ifndef MODULE
|
2012-12-22 04:07:39 +07:00
|
|
|
static int uvesafb_setup(char *options)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
char *this_opt;
|
|
|
|
|
|
|
|
if (!options || !*options)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
while ((this_opt = strsep(&options, ",")) != NULL) {
|
|
|
|
if (!*this_opt) continue;
|
|
|
|
|
|
|
|
if (!strcmp(this_opt, "redraw"))
|
|
|
|
ypan = 0;
|
|
|
|
else if (!strcmp(this_opt, "ypan"))
|
|
|
|
ypan = 1;
|
|
|
|
else if (!strcmp(this_opt, "ywrap"))
|
|
|
|
ypan = 2;
|
|
|
|
else if (!strcmp(this_opt, "vgapal"))
|
|
|
|
pmi_setpal = 0;
|
|
|
|
else if (!strcmp(this_opt, "pmipal"))
|
|
|
|
pmi_setpal = 1;
|
|
|
|
else if (!strncmp(this_opt, "mtrr:", 5))
|
|
|
|
mtrr = simple_strtoul(this_opt+5, NULL, 0);
|
|
|
|
else if (!strcmp(this_opt, "nomtrr"))
|
|
|
|
mtrr = 0;
|
|
|
|
else if (!strcmp(this_opt, "nocrtc"))
|
|
|
|
nocrtc = 1;
|
|
|
|
else if (!strcmp(this_opt, "noedid"))
|
|
|
|
noedid = 1;
|
|
|
|
else if (!strcmp(this_opt, "noblank"))
|
|
|
|
blank = 0;
|
|
|
|
else if (!strncmp(this_opt, "vtotal:", 7))
|
|
|
|
vram_total = simple_strtoul(this_opt + 7, NULL, 0);
|
|
|
|
else if (!strncmp(this_opt, "vremap:", 7))
|
|
|
|
vram_remap = simple_strtoul(this_opt + 7, NULL, 0);
|
|
|
|
else if (!strncmp(this_opt, "maxhf:", 6))
|
|
|
|
maxhf = simple_strtoul(this_opt + 6, NULL, 0);
|
|
|
|
else if (!strncmp(this_opt, "maxvf:", 6))
|
|
|
|
maxvf = simple_strtoul(this_opt + 6, NULL, 0);
|
|
|
|
else if (!strncmp(this_opt, "maxclk:", 7))
|
|
|
|
maxclk = simple_strtoul(this_opt + 7, NULL, 0);
|
|
|
|
else if (!strncmp(this_opt, "vbemode:", 8))
|
|
|
|
vbemode = simple_strtoul(this_opt + 8, NULL, 0);
|
|
|
|
else if (this_opt[0] >= '0' && this_opt[0] <= '9') {
|
|
|
|
mode_option = this_opt;
|
|
|
|
} else {
|
|
|
|
printk(KERN_WARNING
|
|
|
|
"uvesafb: unrecognized option %s\n", this_opt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif /* !MODULE */
|
|
|
|
|
|
|
|
static ssize_t show_v86d(struct device_driver *dev, char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", v86d_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t store_v86d(struct device_driver *dev, const char *buf,
|
|
|
|
size_t count)
|
|
|
|
{
|
|
|
|
strncpy(v86d_path, buf, PATH_MAX);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DRIVER_ATTR(v86d, S_IRUGO | S_IWUSR, show_v86d, store_v86d);
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static int uvesafb_init(void)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
#ifndef MODULE
|
|
|
|
char *option = NULL;
|
|
|
|
|
|
|
|
if (fb_get_options("uvesafb", &option))
|
|
|
|
return -ENODEV;
|
|
|
|
uvesafb_setup(option);
|
|
|
|
#endif
|
|
|
|
err = cn_add_callback(&uvesafb_cn_id, "uvesafb", uvesafb_cn_callback);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = platform_driver_register(&uvesafb_driver);
|
|
|
|
|
|
|
|
if (!err) {
|
|
|
|
uvesafb_device = platform_device_alloc("uvesafb", 0);
|
|
|
|
if (uvesafb_device)
|
|
|
|
err = platform_device_add(uvesafb_device);
|
|
|
|
else
|
|
|
|
err = -ENOMEM;
|
|
|
|
|
|
|
|
if (err) {
|
2013-03-30 09:53:00 +07:00
|
|
|
if (uvesafb_device)
|
|
|
|
platform_device_put(uvesafb_device);
|
2007-10-16 15:28:26 +07:00
|
|
|
platform_driver_unregister(&uvesafb_driver);
|
|
|
|
cn_del_callback(&uvesafb_cn_id);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = driver_create_file(&uvesafb_driver.driver,
|
|
|
|
&driver_attr_v86d);
|
|
|
|
if (err) {
|
|
|
|
printk(KERN_WARNING "uvesafb: failed to register "
|
|
|
|
"attributes\n");
|
|
|
|
err = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(uvesafb_init);
|
|
|
|
|
2012-12-22 04:07:39 +07:00
|
|
|
static void uvesafb_exit(void)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
struct uvesafb_ktask *task;
|
|
|
|
|
|
|
|
if (v86d_started) {
|
|
|
|
task = uvesafb_prep();
|
|
|
|
if (task) {
|
|
|
|
task->t.flags = TF_EXIT;
|
|
|
|
uvesafb_exec(task);
|
|
|
|
uvesafb_free(task);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cn_del_callback(&uvesafb_cn_id);
|
|
|
|
driver_remove_file(&uvesafb_driver.driver, &driver_attr_v86d);
|
|
|
|
platform_device_unregister(uvesafb_device);
|
|
|
|
platform_driver_unregister(&uvesafb_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_exit(uvesafb_exit);
|
|
|
|
|
2010-08-12 12:04:12 +07:00
|
|
|
static int param_set_scroll(const char *val, const struct kernel_param *kp)
|
2007-10-16 15:28:26 +07:00
|
|
|
{
|
|
|
|
ypan = 0;
|
|
|
|
|
|
|
|
if (!strcmp(val, "redraw"))
|
|
|
|
ypan = 0;
|
|
|
|
else if (!strcmp(val, "ypan"))
|
|
|
|
ypan = 1;
|
|
|
|
else if (!strcmp(val, "ywrap"))
|
|
|
|
ypan = 2;
|
2009-06-13 10:46:58 +07:00
|
|
|
else
|
|
|
|
return -EINVAL;
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2010-08-12 12:04:12 +07:00
|
|
|
static struct kernel_param_ops param_ops_scroll = {
|
|
|
|
.set = param_set_scroll,
|
|
|
|
};
|
2008-02-06 16:39:22 +07:00
|
|
|
#define param_check_scroll(name, p) __param_check(name, p, void)
|
2007-10-16 15:28:26 +07:00
|
|
|
|
|
|
|
module_param_named(scroll, ypan, scroll, 0);
|
|
|
|
MODULE_PARM_DESC(scroll,
|
2008-02-06 16:39:22 +07:00
|
|
|
"Scrolling mode, set to 'redraw', 'ypan', or 'ywrap'");
|
2007-10-16 15:28:26 +07:00
|
|
|
module_param_named(vgapal, pmi_setpal, invbool, 0);
|
|
|
|
MODULE_PARM_DESC(vgapal, "Set palette using VGA registers");
|
|
|
|
module_param_named(pmipal, pmi_setpal, bool, 0);
|
|
|
|
MODULE_PARM_DESC(pmipal, "Set palette using PMI calls");
|
|
|
|
module_param(mtrr, uint, 0);
|
|
|
|
MODULE_PARM_DESC(mtrr,
|
|
|
|
"Memory Type Range Registers setting. Use 0 to disable.");
|
|
|
|
module_param(blank, bool, 0);
|
|
|
|
MODULE_PARM_DESC(blank, "Enable hardware blanking");
|
|
|
|
module_param(nocrtc, bool, 0);
|
|
|
|
MODULE_PARM_DESC(nocrtc, "Ignore CRTC timings when setting modes");
|
|
|
|
module_param(noedid, bool, 0);
|
|
|
|
MODULE_PARM_DESC(noedid,
|
|
|
|
"Ignore EDID-provided monitor limits when setting modes");
|
|
|
|
module_param(vram_remap, uint, 0);
|
|
|
|
MODULE_PARM_DESC(vram_remap, "Set amount of video memory to be used [MiB]");
|
|
|
|
module_param(vram_total, uint, 0);
|
|
|
|
MODULE_PARM_DESC(vram_total, "Set total amount of video memoery [MiB]");
|
|
|
|
module_param(maxclk, ushort, 0);
|
|
|
|
MODULE_PARM_DESC(maxclk, "Maximum pixelclock [MHz], overrides EDID data");
|
|
|
|
module_param(maxhf, ushort, 0);
|
|
|
|
MODULE_PARM_DESC(maxhf,
|
|
|
|
"Maximum horizontal frequency [kHz], overrides EDID data");
|
|
|
|
module_param(maxvf, ushort, 0);
|
|
|
|
MODULE_PARM_DESC(maxvf,
|
|
|
|
"Maximum vertical frequency [Hz], overrides EDID data");
|
2008-07-24 11:31:21 +07:00
|
|
|
module_param(mode_option, charp, 0);
|
|
|
|
MODULE_PARM_DESC(mode_option,
|
2007-10-16 15:28:26 +07:00
|
|
|
"Specify initial video mode as \"<xres>x<yres>[-<bpp>][@<refresh>]\"");
|
|
|
|
module_param(vbemode, ushort, 0);
|
|
|
|
MODULE_PARM_DESC(vbemode,
|
|
|
|
"VBE mode number to set, overrides the 'mode' option");
|
|
|
|
module_param_string(v86d, v86d_path, PATH_MAX, 0660);
|
|
|
|
MODULE_PARM_DESC(v86d, "Path to the v86d userspace helper.");
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_AUTHOR("Michal Januszewski <spock@gentoo.org>");
|
|
|
|
MODULE_DESCRIPTION("Framebuffer driver for VBE2.0+ compliant graphics boards");
|
|
|
|
|