blob: 2095e7a9df25c3146379f6c9fbf213d54d846916 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qscreenlinuxfb_qws.h"
#ifndef QT_NO_QWS_LINUXFB
//#include "qmemorymanager_qws.h"
#include "qwsdisplay_qws.h"
#include "qpixmap.h"
#include <private/qwssignalhandler_p.h>
#include <private/qcore_unix_p.h> // overrides QT_OPEN
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/kd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <limits.h>
#include <signal.h>
#include "qwindowsystem_qws.h"
#if !defined(Q_OS_DARWIN) && !defined(Q_OS_FREEBSD)
#include <linux/fb.h>
#ifdef __i386__
#include <asm/mtrr.h>
#endif
#endif
QT_BEGIN_NAMESPACE
extern int qws_client_id;
//#define DEBUG_CACHE
class QLinuxFbScreenPrivate : public QObject
{
public:
QLinuxFbScreenPrivate();
~QLinuxFbScreenPrivate();
void openTty();
void closeTty();
int fd;
int startupw;
int startuph;
int startupd;
bool blank;
QLinuxFbScreen::DriverTypes driverType;
bool doGraphicsMode;
#ifdef QT_QWS_DEPTH_GENERIC
bool doGenericColors;
#endif
int ttyfd;
long oldKdMode;
QString ttyDevice;
QString displaySpec;
};
QLinuxFbScreenPrivate::QLinuxFbScreenPrivate()
: fd(-1), blank(true), doGraphicsMode(true),
#ifdef QT_QWS_DEPTH_GENERIC
doGenericColors(false),
#endif
ttyfd(-1), oldKdMode(KD_TEXT)
{
QWSSignalHandler::instance()->addObject(this);
}
QLinuxFbScreenPrivate::~QLinuxFbScreenPrivate()
{
closeTty();
}
void QLinuxFbScreenPrivate::openTty()
{
const char *const devs[] = {"/dev/tty0", "/dev/tty", "/dev/console", 0};
if (ttyDevice.isEmpty()) {
for (const char * const *dev = devs; *dev; ++dev) {
ttyfd = QT_OPEN(*dev, O_RDWR);
if (ttyfd != -1)
break;
}
} else {
ttyfd = QT_OPEN(ttyDevice.toAscii().constData(), O_RDWR);
}
if (ttyfd == -1)
return;
if (doGraphicsMode) {
ioctl(ttyfd, KDGETMODE, &oldKdMode);
if (oldKdMode != KD_GRAPHICS) {
int ret = ioctl(ttyfd, KDSETMODE, KD_GRAPHICS);
if (ret == -1)
doGraphicsMode = false;
}
}
// No blankin' screen, no blinkin' cursor!, no cursor!
const char termctl[] = "\033[9;0]\033[?33l\033[?25l\033[?1c";
QT_WRITE(ttyfd, termctl, sizeof(termctl));
}
void QLinuxFbScreenPrivate::closeTty()
{
if (ttyfd == -1)
return;
if (doGraphicsMode)
ioctl(ttyfd, KDSETMODE, oldKdMode);
// Blankin' screen, blinkin' cursor!
const char termctl[] = "\033[9;15]\033[?33h\033[?25h\033[?0c";
QT_WRITE(ttyfd, termctl, sizeof(termctl));
QT_CLOSE(ttyfd);
ttyfd = -1;
}
/*!
\enum QLinuxFbScreen::DriverTypes
This enum describes the driver type.
\value GenericDriver Generic Linux framebuffer driver
\value EInk8Track e-Ink framebuffer driver using the 8Track chipset
*/
/*!
\fn QLinuxFbScreen::fixupScreenInfo(fb_fix_screeninfo &finfo, fb_var_screeninfo &vinfo)
Adjust the values returned by the framebuffer driver, to work
around driver bugs or nonstandard behavior in certain drivers.
\a finfo and \a vinfo specify the fixed and variable screen info
returned by the driver.
*/
void QLinuxFbScreen::fixupScreenInfo(fb_fix_screeninfo &finfo, fb_var_screeninfo &vinfo)
{
// 8Track e-ink devices (as found in Sony PRS-505) lie
// about their bit depth -- they claim they're 1 bit per
// pixel while the only supported mode is 8 bit per pixel
// grayscale.
// Caused by this, they also miscalculate their line length.
if(!strcmp(finfo.id, "8TRACKFB") && vinfo.bits_per_pixel == 1) {
vinfo.bits_per_pixel = 8;
finfo.line_length = vinfo.xres;
}
}
/*!
\internal
\class QLinuxFbScreen
\ingroup qws
\brief The QLinuxFbScreen class implements a screen driver for the
Linux framebuffer.
Note that this class is only available in \l{Qt for Embedded Linux}.
Custom screen drivers can be added by subclassing the
QScreenDriverPlugin class, using the QScreenDriverFactory class to
dynamically load the driver into the application, but there should
only be one screen object per application.
The QLinuxFbScreen class provides the cache() function allocating
off-screen graphics memory, and the complementary uncache()
function releasing the allocated memory. The latter function will
first sync the graphics card to ensure the memory isn't still
being used by a command in the graphics card FIFO queue. The
deleteEntry() function deletes the given memory block without such
synchronization. Given the screen instance and client id, the
memory can also be released using the clearCache() function, but
this should only be necessary if a client exits abnormally.
In addition, when in paletted graphics modes, the set() function
provides the possibility of setting a specified color index to a
given RGB value.
The QLinuxFbScreen class also acts as a factory for the
unaccelerated screen cursor and the unaccelerated raster-based
implementation of QPaintEngine (\c QRasterPaintEngine);
accelerated drivers for Linux should derive from this class.
\sa QScreen, QScreenDriverPlugin, {Running Applications}
*/
/*!
\fn bool QLinuxFbScreen::useOffscreen()
\internal
*/
// Unaccelerated screen/driver setup. Can be overridden by accelerated
// drivers
/*!
\fn QLinuxFbScreen::QLinuxFbScreen(int displayId)
Constructs a QLinuxFbScreen object. The \a displayId argument
identifies the Qt for Embedded Linux server to connect to.
*/
QLinuxFbScreen::QLinuxFbScreen(int display_id)
: QScreen(display_id, LinuxFBClass), d_ptr(new QLinuxFbScreenPrivate)
{
canaccel=false;
clearCacheFunc = &clearCache;
#ifdef QT_QWS_CLIENTBLIT
setSupportsBlitInClients(true);
#endif
}
/*!
Destroys this QLinuxFbScreen object.
*/
QLinuxFbScreen::~QLinuxFbScreen()
{
}
/*!
\reimp
This is called by \l{Qt for Embedded Linux} clients to map in the framebuffer.
It should be reimplemented by accelerated drivers to map in
graphics card registers; those drivers should then call this
function in order to set up offscreen memory management. The
device is specified in \a displaySpec; e.g. "/dev/fb".
\sa disconnect()
*/
bool QLinuxFbScreen::connect(const QString &displaySpec)
{
d_ptr->displaySpec = displaySpec;
const QStringList args = displaySpec.split(QLatin1Char(':'));
if (args.contains(QLatin1String("nographicsmodeswitch")))
d_ptr->doGraphicsMode = false;
#ifdef QT_QWS_DEPTH_GENERIC
if (args.contains(QLatin1String("genericcolors")))
d_ptr->doGenericColors = true;
#endif
QRegExp ttyRegExp(QLatin1String("tty=(.*)"));
if (args.indexOf(ttyRegExp) != -1)
d_ptr->ttyDevice = ttyRegExp.cap(1);
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
#ifndef QT_QWS_FRAMEBUFFER_LITTLE_ENDIAN
if (args.contains(QLatin1String("littleendian")))
#endif
QScreen::setFrameBufferLittleEndian(true);
#endif
QString dev = QLatin1String("/dev/fb0");
foreach(QString d, args) {
if (d.startsWith(QLatin1Char('/'))) {
dev = d;
break;
}
}
if (access(dev.toLatin1().constData(), R_OK|W_OK) == 0)
d_ptr->fd = QT_OPEN(dev.toLatin1().constData(), O_RDWR);
if (d_ptr->fd == -1) {
if (QApplication::type() == QApplication::GuiServer) {
perror("QScreenLinuxFb::connect");
qCritical("Error opening framebuffer device %s", qPrintable(dev));
return false;
}
if (access(dev.toLatin1().constData(), R_OK) == 0)
d_ptr->fd = QT_OPEN(dev.toLatin1().constData(), O_RDONLY);
}
::fb_fix_screeninfo finfo;
::fb_var_screeninfo vinfo;
//#######################
// Shut up Valgrind
memset(&vinfo, 0, sizeof(vinfo));
memset(&finfo, 0, sizeof(finfo));
//#######################
/* Get fixed screen information */
if (d_ptr->fd != -1 && ioctl(d_ptr->fd, FBIOGET_FSCREENINFO, &finfo)) {
perror("QLinuxFbScreen::connect");
qWarning("Error reading fixed information");
return false;
}
d_ptr->driverType = strcmp(finfo.id, "8TRACKFB") ? GenericDriver : EInk8Track;
if (finfo.type == FB_TYPE_VGA_PLANES) {
qWarning("VGA16 video mode not supported");
return false;
}
/* Get variable screen information */
if (d_ptr->fd != -1 && ioctl(d_ptr->fd, FBIOGET_VSCREENINFO, &vinfo)) {
perror("QLinuxFbScreen::connect");
qWarning("Error reading variable information");
return false;
}
fixupScreenInfo(finfo, vinfo);
grayscale = vinfo.grayscale;
d = vinfo.bits_per_pixel;
if (d == 24) {
d = vinfo.red.length + vinfo.green.length + vinfo.blue.length;
if (d <= 0)
d = 24; // reset if color component lengths are not reported
} else if (d == 16) {
d = vinfo.red.length + vinfo.green.length + vinfo.blue.length;
if (d <= 0)
d = 16;
}
lstep = finfo.line_length;
int xoff = vinfo.xoffset;
int yoff = vinfo.yoffset;
const char* qwssize;
if((qwssize=::getenv("QWS_SIZE")) && sscanf(qwssize,"%dx%d",&w,&h)==2) {
if (d_ptr->fd != -1) {
if ((uint)w > vinfo.xres) w = vinfo.xres;
if ((uint)h > vinfo.yres) h = vinfo.yres;
}
dw=w;
dh=h;
int xxoff, yyoff;
if (sscanf(qwssize, "%*dx%*d+%d+%d", &xxoff, &yyoff) == 2) {
if (xxoff < 0 || xxoff + w > vinfo.xres)
xxoff = vinfo.xres - w;
if (yyoff < 0 || yyoff + h > vinfo.yres)
yyoff = vinfo.yres - h;
xoff += xxoff;
yoff += yyoff;
} else {
xoff += (vinfo.xres - w)/2;
yoff += (vinfo.yres - h)/2;
}
} else {
dw=w=vinfo.xres;
dh=h=vinfo.yres;
}
if (w == 0 || h == 0) {
qWarning("QScreenLinuxFb::connect(): Unable to find screen geometry, "
"will use 320x240.");
dw = w = 320;
dh = h = 240;
}
setPixelFormat(vinfo);
// Handle display physical size spec.
QStringList displayArgs = displaySpec.split(QLatin1Char(':'));
QRegExp mmWidthRx(QLatin1String("mmWidth=?(\\d+)"));
int dimIdxW = displayArgs.indexOf(mmWidthRx);
QRegExp mmHeightRx(QLatin1String("mmHeight=?(\\d+)"));
int dimIdxH = displayArgs.indexOf(mmHeightRx);
if (dimIdxW >= 0) {
mmWidthRx.exactMatch(displayArgs.at(dimIdxW));
physWidth = mmWidthRx.cap(1).toInt();
if (dimIdxH < 0)
physHeight = dh*physWidth/dw;
}
if (dimIdxH >= 0) {
mmHeightRx.exactMatch(displayArgs.at(dimIdxH));
physHeight = mmHeightRx.cap(1).toInt();
if (dimIdxW < 0)
physWidth = dw*physHeight/dh;
}
if (dimIdxW < 0 && dimIdxH < 0) {
if (vinfo.width != 0 && vinfo.height != 0
&& vinfo.width != UINT_MAX && vinfo.height != UINT_MAX) {
physWidth = vinfo.width;
physHeight = vinfo.height;
} else {
const int dpi = 72;
physWidth = qRound(dw * 25.4 / dpi);
physHeight = qRound(dh * 25.4 / dpi);
}
}
dataoffset = yoff * lstep + xoff * d / 8;
//qDebug("Using %dx%dx%d screen",w,h,d);
/* Figure out the size of the screen in bytes */
size = h * lstep;
mapsize = finfo.smem_len;
data = (unsigned char *)-1;
if (d_ptr->fd != -1)
data = (unsigned char *)mmap(0, mapsize, PROT_READ | PROT_WRITE,
MAP_SHARED, d_ptr->fd, 0);
if ((long)data == -1) {
if (QApplication::type() == QApplication::GuiServer) {
perror("QLinuxFbScreen::connect");
qWarning("Error: failed to map framebuffer device to memory.");
return false;
}
data = 0;
} else {
data += dataoffset;
}
canaccel = useOffscreen();
if(canaccel)
setupOffScreen();
// Now read in palette
if((vinfo.bits_per_pixel==8) || (vinfo.bits_per_pixel==4)) {
screencols= (vinfo.bits_per_pixel==8) ? 256 : 16;
int loopc;
::fb_cmap startcmap;
startcmap.start=0;
startcmap.len=screencols;
startcmap.red=(unsigned short int *)
malloc(sizeof(unsigned short int)*screencols);
startcmap.green=(unsigned short int *)
malloc(sizeof(unsigned short int)*screencols);
startcmap.blue=(unsigned short int *)
malloc(sizeof(unsigned short int)*screencols);
startcmap.transp=(unsigned short int *)
malloc(sizeof(unsigned short int)*screencols);
if (d_ptr->fd == -1 || ioctl(d_ptr->fd, FBIOGETCMAP, &startcmap)) {
perror("QLinuxFbScreen::connect");
qWarning("Error reading palette from framebuffer, using default palette");
createPalette(startcmap, vinfo, finfo);
}
int bits_used = 0;
for(loopc=0;loopc<screencols;loopc++) {
screenclut[loopc]=qRgb(startcmap.red[loopc] >> 8,
startcmap.green[loopc] >> 8,
startcmap.blue[loopc] >> 8);
bits_used |= startcmap.red[loopc]
| startcmap.green[loopc]
| startcmap.blue[loopc];
}
// WORKAROUND: Some framebuffer drivers only return 8 bit
// color values, so we need to not bit shift them..
if ((bits_used & 0x00ff) && !(bits_used & 0xff00)) {
for(loopc=0;loopc<screencols;loopc++) {
screenclut[loopc] = qRgb(startcmap.red[loopc],
startcmap.green[loopc],
startcmap.blue[loopc]);
}
qWarning("8 bits cmap returned due to faulty FB driver, colors corrected");
}
free(startcmap.red);
free(startcmap.green);
free(startcmap.blue);
free(startcmap.transp);
} else {
screencols=0;
}
return true;
}
/*!
\reimp
This unmaps the framebuffer.
\sa connect()
*/
void QLinuxFbScreen::disconnect()
{
data -= dataoffset;
if (data)
munmap((char*)data,mapsize);
close(d_ptr->fd);
}
// #define DEBUG_VINFO
void QLinuxFbScreen::createPalette(fb_cmap &cmap, fb_var_screeninfo &vinfo, fb_fix_screeninfo &finfo)
{
if((vinfo.bits_per_pixel==8) || (vinfo.bits_per_pixel==4)) {
screencols= (vinfo.bits_per_pixel==8) ? 256 : 16;
cmap.start=0;
cmap.len=screencols;
cmap.red=(unsigned short int *)
malloc(sizeof(unsigned short int)*screencols);
cmap.green=(unsigned short int *)
malloc(sizeof(unsigned short int)*screencols);
cmap.blue=(unsigned short int *)
malloc(sizeof(unsigned short int)*screencols);
cmap.transp=(unsigned short int *)
malloc(sizeof(unsigned short int)*screencols);
if (screencols==16) {
if (finfo.type == FB_TYPE_PACKED_PIXELS) {
// We'll setup a grayscale cmap for 4bpp linear
int val = 0;
for (int idx = 0; idx < 16; ++idx, val += 17) {
cmap.red[idx] = (val<<8)|val;
cmap.green[idx] = (val<<8)|val;
cmap.blue[idx] = (val<<8)|val;
screenclut[idx]=qRgb(val, val, val);
}
} else {
// Default 16 colour palette
// Green is now trolltech green so certain images look nicer
// black d_gray l_gray white red green blue cyan magenta yellow
unsigned char reds[16] = { 0x00, 0x7F, 0xBF, 0xFF, 0xFF, 0xA2, 0x00, 0xFF, 0xFF, 0x00, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x82 };
unsigned char greens[16] = { 0x00, 0x7F, 0xBF, 0xFF, 0x00, 0xC5, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x7F };
unsigned char blues[16] = { 0x00, 0x7F, 0xBF, 0xFF, 0x00, 0x11, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x7F, 0x7F, 0x7F, 0x00, 0x00 };
for (int idx = 0; idx < 16; ++idx) {
cmap.red[idx] = ((reds[idx]) << 8)|reds[idx];
cmap.green[idx] = ((greens[idx]) << 8)|greens[idx];
cmap.blue[idx] = ((blues[idx]) << 8)|blues[idx];
cmap.transp[idx] = 0;
screenclut[idx]=qRgb(reds[idx], greens[idx], blues[idx]);
}
}
} else {
if (grayscale) {
// Build grayscale palette
int i;
for(i=0;i<screencols;++i) {
int bval = screencols == 256 ? i : (i << 4);
ushort val = (bval << 8) | bval;
cmap.red[i] = val;
cmap.green[i] = val;
cmap.blue[i] = val;
cmap.transp[i] = 0;
screenclut[i] = qRgb(bval,bval,bval);
}
} else {
// 6x6x6 216 color cube
int idx = 0;
for(int ir = 0x0; ir <= 0xff; ir+=0x33) {
for(int ig = 0x0; ig <= 0xff; ig+=0x33) {
for(int ib = 0x0; ib <= 0xff; ib+=0x33) {
cmap.red[idx] = (ir << 8)|ir;
cmap.green[idx] = (ig << 8)|ig;
cmap.blue[idx] = (ib << 8)|ib;
cmap.transp[idx] = 0;
screenclut[idx]=qRgb(ir, ig, ib);
++idx;
}
}
}
// Fill in rest with 0
for (int loopc=0; loopc<40; ++loopc) {
screenclut[idx]=0;
++idx;
}
screencols=idx;
}
}
} else if(finfo.visual==FB_VISUAL_DIRECTCOLOR) {
cmap.start=0;
int rbits=0,gbits=0,bbits=0;
switch (vinfo.bits_per_pixel) {
case 8:
rbits=vinfo.red.length;
gbits=vinfo.green.length;
bbits=vinfo.blue.length;
if(rbits==0 && gbits==0 && bbits==0) {
// cyber2000 driver bug hack
rbits=3;
gbits=3;
bbits=2;
}
break;
case 15:
rbits=5;
gbits=5;
bbits=5;
break;
case 16:
rbits=5;
gbits=6;
bbits=5;
break;
case 18:
case 19:
rbits=6;
gbits=6;
bbits=6;
break;
case 24: case 32:
rbits=gbits=bbits=8;
break;
}
screencols=cmap.len=1<<qMax(rbits,qMax(gbits,bbits));
cmap.red=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
cmap.green=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
cmap.blue=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
cmap.transp=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
for(unsigned int i = 0x0; i < cmap.len; i++) {
cmap.red[i] = i*65535/((1<<rbits)-1);
cmap.green[i] = i*65535/((1<<gbits)-1);
cmap.blue[i] = i*65535/((1<<bbits)-1);
cmap.transp[i] = 0;
}
}
}
/*!
\reimp
This is called by the \l{Qt for Embedded Linux} server at startup time.
It turns off console blinking, sets up the color palette, enables write
combining on the framebuffer and initialises the off-screen memory
manager.
*/
bool QLinuxFbScreen::initDevice()
{
d_ptr->openTty();
// Grab current mode so we can reset it
fb_var_screeninfo vinfo;
fb_fix_screeninfo finfo;
//#######################
// Shut up Valgrind
memset(&vinfo, 0, sizeof(vinfo));
memset(&finfo, 0, sizeof(finfo));
//#######################
if (ioctl(d_ptr->fd, FBIOGET_VSCREENINFO, &vinfo)) {
perror("QLinuxFbScreen::initDevice");
qFatal("Error reading variable information in card init");
return false;
}
#ifdef DEBUG_VINFO
qDebug("Greyscale %d",vinfo.grayscale);
qDebug("Nonstd %d",vinfo.nonstd);
qDebug("Red %d %d %d",vinfo.red.offset,vinfo.red.length,
vinfo.red.msb_right);
qDebug("Green %d %d %d",vinfo.green.offset,vinfo.green.length,
vinfo.green.msb_right);
qDebug("Blue %d %d %d",vinfo.blue.offset,vinfo.blue.length,
vinfo.blue.msb_right);
qDebug("Transparent %d %d %d",vinfo.transp.offset,vinfo.transp.length,
vinfo.transp.msb_right);
#endif
if (ioctl(d_ptr->fd, FBIOGET_FSCREENINFO, &finfo)) {
perror("QLinuxFbScreen::initDevice");
qCritical("Error reading fixed information in card init");
// It's not an /error/ as such, though definitely a bad sign
// so we return true
return true;
}
fixupScreenInfo(finfo, vinfo);
d_ptr->startupw=vinfo.xres;
d_ptr->startuph=vinfo.yres;
d_ptr->startupd=vinfo.bits_per_pixel;
grayscale = vinfo.grayscale;
#ifdef __i386__
// Now init mtrr
if(!::getenv("QWS_NOMTRR")) {
int mfd=QT_OPEN("/proc/mtrr",O_WRONLY,0);
// MTRR entry goes away when file is closed - i.e.
// hopefully when QWS is killed
if(mfd != -1) {
mtrr_sentry sentry;
sentry.base=(unsigned long int)finfo.smem_start;
//qDebug("Physical framebuffer address %p",(void*)finfo.smem_start);
// Size needs to be in 4k chunks, but that's not always
// what we get thanks to graphics card registers. Write combining
// these is Not Good, so we write combine what we can
// (which is not much - 4 megs on an 8 meg card, it seems)
unsigned int size=finfo.smem_len;
size=size >> 22;
size=size << 22;
sentry.size=size;
sentry.type=MTRR_TYPE_WRCOMB;
if(ioctl(mfd,MTRRIOC_ADD_ENTRY,&sentry)==-1) {
//printf("Couldn't add mtrr entry for %lx %lx, %s\n",
//sentry.base,sentry.size,strerror(errno));
}
}
// Should we close mfd here?
//QT_CLOSE(mfd);
}
#endif
if ((vinfo.bits_per_pixel==8) || (vinfo.bits_per_pixel==4) || (finfo.visual==FB_VISUAL_DIRECTCOLOR))
{
fb_cmap cmap;
createPalette(cmap, vinfo, finfo);
if (ioctl(d_ptr->fd, FBIOPUTCMAP, &cmap)) {
perror("QLinuxFbScreen::initDevice");
qWarning("Error writing palette to framebuffer");
}
free(cmap.red);
free(cmap.green);
free(cmap.blue);
free(cmap.transp);
}
if (canaccel) {
*entryp=0;
*lowest = mapsize;
insert_entry(*entryp, *lowest, *lowest); // dummy entry to mark start
}
shared->fifocount = 0;
shared->buffer_offset = 0xffffffff; // 0 would be a sensible offset (screen)
shared->linestep = 0;
shared->cliptop = 0xffffffff;
shared->clipleft = 0xffffffff;
shared->clipright = 0xffffffff;
shared->clipbottom = 0xffffffff;
shared->rop = 0xffffffff;
#ifdef QT_QWS_DEPTH_GENERIC
if (pixelFormat() == QImage::Format_Invalid && screencols == 0
&& d_ptr->doGenericColors)
{
qt_set_generic_blit(this, vinfo.bits_per_pixel,
vinfo.red.length, vinfo.green.length,
vinfo.blue.length, vinfo.transp.length,
vinfo.red.offset, vinfo.green.offset,
vinfo.blue.offset, vinfo.transp.offset);
}
#endif
#ifndef QT_NO_QWS_CURSOR
QScreenCursor::initSoftwareCursor();
#endif
blank(false);
return true;
}
/*
The offscreen memory manager's list of entries is stored at the bottom
of the offscreen memory area and consistes of a series of QPoolEntry's,
each of which keep track of a block of allocated memory. Unallocated memory
is implicitly indicated by the gap between blocks indicated by QPoolEntry's.
The memory manager looks through any unallocated memory before the end
of currently-allocated memory to see if a new block will fit in the gap;
if it doesn't it allocated it from the end of currently-allocated memory.
Memory is allocated from the top of the framebuffer downwards; if it hits
the list of entries then offscreen memory is full and further allocations
are made from main RAM (and hence unaccelerated). Allocated memory can
be seen as a sort of upside-down stack; lowest keeps track of the
bottom of the stack.
*/
void QLinuxFbScreen::delete_entry(int pos)
{
if (pos > *entryp || pos < 0) {
qWarning("Attempt to delete odd pos! %d %d", pos, *entryp);
return;
}
#ifdef DEBUG_CACHE
qDebug("Remove entry: %d", pos);
#endif
QPoolEntry *qpe = &entries[pos];
if (qpe->start <= *lowest) {
// Lowest goes up again
*lowest = entries[pos-1].start;
#ifdef DEBUG_CACHE
qDebug(" moved lowest to %d", *lowest);
#endif
}
(*entryp)--;
if (pos == *entryp)
return;
int size = (*entryp)-pos;
memmove(&entries[pos], &entries[pos+1], size*sizeof(QPoolEntry));
}
void QLinuxFbScreen::insert_entry(int pos, int start, int end)
{
if (pos > *entryp) {
qWarning("Attempt to insert odd pos! %d %d",pos,*entryp);
return;
}
#ifdef DEBUG_CACHE
qDebug("Insert entry: %d, %d -> %d", pos, start, end);
#endif
if (start < (int)*lowest) {
*lowest = start;
#ifdef DEBUG_CACHE
qDebug(" moved lowest to %d", *lowest);
#endif
}
if (pos == *entryp) {
entries[pos].start = start;
entries[pos].end = end;
entries[pos].clientId = qws_client_id;
(*entryp)++;
return;
}
int size=(*entryp)-pos;
memmove(&entries[pos+1],&entries[pos],size*sizeof(QPoolEntry));
entries[pos].start=start;
entries[pos].end=end;
entries[pos].clientId=qws_client_id;
(*entryp)++;
}
/*!
\fn uchar * QLinuxFbScreen::cache(int amount)
Requests the specified \a amount of offscreen graphics card memory
from the memory manager, and returns a pointer to the data within
the framebuffer (or 0 if there is no free memory).
Note that the display is locked while memory is allocated in order to
preserve the memory pool's integrity.
Use the QScreen::onCard() function to retrieve an offset (in
bytes) from the start of graphics card memory for the returned
pointer.
\sa uncache(), clearCache(), deleteEntry()
*/
uchar * QLinuxFbScreen::cache(int amount)
{
if (!canaccel || entryp == 0)
return 0;
qt_fbdpy->grab();
int startp = cacheStart + (*entryp+1) * sizeof(QPoolEntry);
if (startp >= (int)*lowest) {
// We don't have room for another cache QPoolEntry.
#ifdef DEBUG_CACHE
qDebug("No room for pool entry in VRAM");
#endif
qt_fbdpy->ungrab();
return 0;
}
int align = pixmapOffsetAlignment();
if (*entryp > 1) {
// Try to find a gap in the allocated blocks.
for (int loopc = 0; loopc < *entryp-1; loopc++) {
int freestart = entries[loopc+1].end;
int freeend = entries[loopc].start;
if (freestart != freeend) {
while (freestart % align) {
freestart++;
}
int len=freeend-freestart;
if (len >= amount) {
insert_entry(loopc+1, freestart, freestart+amount);
qt_fbdpy->ungrab();
return data+freestart;
}
}
}
}
// No free blocks in already-taken memory; get some more
// if we can
int newlowest = (*lowest)-amount;
if (newlowest % align) {
newlowest -= align;
while (newlowest % align) {
newlowest++;
}
}
if (startp >= newlowest) {
qt_fbdpy->ungrab();
#ifdef DEBUG_CACHE
qDebug("No VRAM available for %d bytes", amount);
#endif
return 0;
}
insert_entry(*entryp, newlowest, *lowest);
qt_fbdpy->ungrab();
return data + newlowest;
}
/*!
\fn void QLinuxFbScreen::uncache(uchar * memoryBlock)
Deletes the specified \a memoryBlock allocated from the graphics
card memory.
Note that the display is locked while memory is unallocated in
order to preserve the memory pool's integrity.
This function will first sync the graphics card to ensure the
memory isn't still being used by a command in the graphics card
FIFO queue. It is possible to speed up a driver by overriding this
function to avoid syncing. For example, the driver might delay
deleting the memory until it detects that all commands dealing
with the memory are no longer in the queue. Note that it will then
be up to the driver to ensure that the specified \a memoryBlock no
longer is being used.
\sa cache(), deleteEntry(), clearCache()
*/
void QLinuxFbScreen::uncache(uchar * c)
{
// need to sync graphics card
deleteEntry(c);
}
/*!
\fn void QLinuxFbScreen::deleteEntry(uchar * memoryBlock)
Deletes the specified \a memoryBlock allocated from the graphics
card memory.
\sa uncache(), cache(), clearCache()
*/
void QLinuxFbScreen::deleteEntry(uchar * c)
{
qt_fbdpy->grab();
unsigned long pos=(unsigned long)c;
pos-=((unsigned long)data);
unsigned int hold=(*entryp);
for(unsigned int loopc=1;loopc<hold;loopc++) {
if (entries[loopc].start==pos) {
if (entries[loopc].clientId == qws_client_id)
delete_entry(loopc);
else
qWarning("Attempt to delete client id %d cache entry",
entries[loopc].clientId);
qt_fbdpy->ungrab();
return;
}
}
qt_fbdpy->ungrab();
qWarning("Attempt to delete unknown offset %ld",pos);
}
/*!
Removes all entries from the cache for the specified screen \a
instance and client identified by the given \a clientId.
Calling this function should only be necessary if a client exits
abnormally.
\sa cache(), uncache(), deleteEntry()
*/
void QLinuxFbScreen::clearCache(QScreen *instance, int clientId)
{
QLinuxFbScreen *screen = (QLinuxFbScreen *)instance;
if (!screen->canaccel || !screen->entryp)
return;
qt_fbdpy->grab();
for (int loopc = 0; loopc < *(screen->entryp); loopc++) {
if (screen->entries[loopc].clientId == clientId) {
screen->delete_entry(loopc);
loopc--;
}
}
qt_fbdpy->ungrab();
}
void QLinuxFbScreen::setupOffScreen()
{
// Figure out position of offscreen memory
// Set up pool entries pointer table and 64-bit align it
int psize = size;
// hw: this causes the limitation of cursors to 64x64
// the cursor should rather use the normal pixmap mechanism
psize += 4096; // cursor data
psize += 8; // for alignment
psize &= ~0x7; // align
unsigned long pos = (unsigned long)data;
pos += psize;
entryp = ((int *)pos);
lowest = ((unsigned int *)pos)+1;
pos += (sizeof(int))*4;
entries = (QPoolEntry *)pos;
// beginning of offscreen memory available for pixmaps.
cacheStart = psize + 4*sizeof(int) + sizeof(QPoolEntry);
}
/*!
\reimp
This is called by the \l{Qt for Embedded Linux} server when it shuts
down, and should be inherited if you need to do any card-specific cleanup.
The default version hides the screen cursor and reenables the blinking
cursor and screen blanking.
*/
void QLinuxFbScreen::shutdownDevice()
{
// Causing crashes. Not needed.
//setMode(startupw,startuph,startupd);
/*
if (startupd == 8) {
ioctl(fd,FBIOPUTCMAP,startcmap);
free(startcmap->red);
free(startcmap->green);
free(startcmap->blue);
free(startcmap->transp);
delete startcmap;
startcmap = 0;
}
*/
d_ptr->closeTty();
}
/*!
\fn void QLinuxFbScreen::set(unsigned int index,unsigned int red,unsigned int green,unsigned int blue)
Sets the specified color \a index to the specified RGB value, (\a
red, \a green, \a blue), when in paletted graphics modes.
*/
void QLinuxFbScreen::set(unsigned int i,unsigned int r,unsigned int g,unsigned int b)
{
if (d_ptr->fd != -1) {
fb_cmap cmap;
cmap.start=i;
cmap.len=1;
cmap.red=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
cmap.green=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
cmap.blue=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
cmap.transp=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
cmap.red[0]=r << 8;
cmap.green[0]=g << 8;
cmap.blue[0]=b << 8;
cmap.transp[0]=0;
ioctl(d_ptr->fd, FBIOPUTCMAP, &cmap);
free(cmap.red);
free(cmap.green);
free(cmap.blue);
free(cmap.transp);
}
screenclut[i] = qRgb(r, g, b);
}
/*!
\reimp
Sets the framebuffer to a new resolution and bit depth. The width is
in \a nw, the height is in \a nh, and the depth is in \a nd. After
doing this any currently-existing paint engines will be invalid and the
screen should be completely redrawn. In a multiple-process
Embedded Qt situation you must signal all other applications to
call setMode() to the same mode and redraw.
*/
void QLinuxFbScreen::setMode(int nw,int nh,int nd)
{
if (d_ptr->fd == -1)
return;
fb_fix_screeninfo finfo;
fb_var_screeninfo vinfo;
//#######################
// Shut up Valgrind
memset(&vinfo, 0, sizeof(vinfo));
memset(&finfo, 0, sizeof(finfo));
//#######################
if (ioctl(d_ptr->fd, FBIOGET_VSCREENINFO, &vinfo)) {
perror("QLinuxFbScreen::setMode");
qFatal("Error reading variable information in mode change");
}
vinfo.xres=nw;
vinfo.yres=nh;
vinfo.bits_per_pixel=nd;
if (ioctl(d_ptr->fd, FBIOPUT_VSCREENINFO, &vinfo)) {
perror("QLinuxFbScreen::setMode");
qCritical("Error writing variable information in mode change");
}
if (ioctl(d_ptr->fd, FBIOGET_VSCREENINFO, &vinfo)) {
perror("QLinuxFbScreen::setMode");
qFatal("Error reading changed variable information in mode change");
}
if (ioctl(d_ptr->fd, FBIOGET_FSCREENINFO, &finfo)) {
perror("QLinuxFbScreen::setMode");
qFatal("Error reading fixed information");
}
fixupScreenInfo(finfo, vinfo);
disconnect();
connect(d_ptr->displaySpec);
exposeRegion(region(), 0);
}
// save the state of the graphics card
// This is needed so that e.g. we can restore the palette when switching
// between linux virtual consoles.
/*!
\reimp
This doesn't do anything; accelerated drivers may wish to reimplement
it to save graphics cards registers. It's called by the
\l{Qt for Embedded Linux} server when the virtual console is switched.
*/
void QLinuxFbScreen::save()
{
// nothing to do.
}
// restore the state of the graphics card.
/*!
\reimp
This is called when the virtual console is switched back to
\l{Qt for Embedded Linux} and restores the palette.
*/
void QLinuxFbScreen::restore()
{
if (d_ptr->fd == -1)
return;
if ((d == 8) || (d == 4)) {
fb_cmap cmap;
cmap.start=0;
cmap.len=screencols;
cmap.red=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
cmap.green=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
cmap.blue=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
cmap.transp=(unsigned short int *)
malloc(sizeof(unsigned short int)*256);
for (int loopc = 0; loopc < screencols; loopc++) {
cmap.red[loopc] = qRed(screenclut[loopc]) << 8;
cmap.green[loopc] = qGreen(screenclut[loopc]) << 8;
cmap.blue[loopc] = qBlue(screenclut[loopc]) << 8;
cmap.transp[loopc] = 0;
}
ioctl(d_ptr->fd, FBIOPUTCMAP, &cmap);
free(cmap.red);
free(cmap.green);
free(cmap.blue);
free(cmap.transp);
}
}
/*!
\fn int QLinuxFbScreen::sharedRamSize(void * end)
\internal
*/
// This works like the QScreenCursor code. end points to the end
// of our shared structure, we return the amount of memory we reserved
int QLinuxFbScreen::sharedRamSize(void * end)
{
shared=(QLinuxFb_Shared *)end;
shared--;
return sizeof(QLinuxFb_Shared);
}
/*!
\reimp
*/
void QLinuxFbScreen::setDirty(const QRect &r)
{
if(d_ptr->driverType == EInk8Track) {
// e-Ink displays need a trigger to actually show what is
// in their framebuffer memory. The 8-Track driver does this
// by adding custom IOCTLs - FBIO_EINK_DISP_PIC (0x46a2) takes
// an argument specifying whether or not to flash the screen
// while updating.
// There doesn't seem to be a way to tell it to just update
// a subset of the screen.
if(r.left() == 0 && r.top() == 0 && r.width() == dw && r.height() == dh)
ioctl(d_ptr->fd, 0x46a2, 1);
else
ioctl(d_ptr->fd, 0x46a2, 0);
}
}
/*!
\reimp
*/
void QLinuxFbScreen::blank(bool on)
{
if (d_ptr->blank == on)
return;
#if defined(QT_QWS_IPAQ)
if (on)
system("apm -suspend");
#else
if (d_ptr->fd == -1)
return;
// Some old kernel versions don't have this. These defines should go
// away eventually
#if defined(FBIOBLANK)
#if defined(VESA_POWERDOWN) && defined(VESA_NO_BLANKING)
ioctl(d_ptr->fd, FBIOBLANK, on ? VESA_POWERDOWN : VESA_NO_BLANKING);
#else
ioctl(d_ptr->fd, FBIOBLANK, on ? 1 : 0);
#endif
#endif
#endif
d_ptr->blank = on;
}
void QLinuxFbScreen::setPixelFormat(struct fb_var_screeninfo info)
{
const fb_bitfield rgba[4] = { info.red, info.green,
info.blue, info.transp };
QImage::Format format = QImage::Format_Invalid;
switch (d) {
case 32: {
const fb_bitfield argb8888[4] = {{16, 8, 0}, {8, 8, 0},
{0, 8, 0}, {24, 8, 0}};
const fb_bitfield abgr8888[4] = {{0, 8, 0}, {8, 8, 0},
{16, 8, 0}, {24, 8, 0}};
if (memcmp(rgba, argb8888, 4 * sizeof(fb_bitfield)) == 0) {
format = QImage::Format_ARGB32;
} else if (memcmp(rgba, argb8888, 3 * sizeof(fb_bitfield)) == 0) {
format = QImage::Format_RGB32;
} else if (memcmp(rgba, abgr8888, 3 * sizeof(fb_bitfield)) == 0) {
format = QImage::Format_RGB32;
pixeltype = QScreen::BGRPixel;
}
break;
}
case 24: {
const fb_bitfield rgb888[4] = {{16, 8, 0}, {8, 8, 0},
{0, 8, 0}, {0, 0, 0}};
const fb_bitfield bgr888[4] = {{0, 8, 0}, {8, 8, 0},
{16, 8, 0}, {0, 0, 0}};
if (memcmp(rgba, rgb888, 3 * sizeof(fb_bitfield)) == 0) {
format = QImage::Format_RGB888;
} else if (memcmp(rgba, bgr888, 3 * sizeof(fb_bitfield)) == 0) {
format = QImage::Format_RGB888;
pixeltype = QScreen::BGRPixel;
}
break;
}
case 18: {
const fb_bitfield rgb666[4] = {{12, 6, 0}, {6, 6, 0},
{0, 6, 0}, {0, 0, 0}};
if (memcmp(rgba, rgb666, 3 * sizeof(fb_bitfield)) == 0)
format = QImage::Format_RGB666;
break;
}
case 16: {
const fb_bitfield rgb565[4] = {{11, 5, 0}, {5, 6, 0},
{0, 5, 0}, {0, 0, 0}};
const fb_bitfield bgr565[4] = {{0, 5, 0}, {5, 6, 0},
{11, 5, 0}, {0, 0, 0}};
if (memcmp(rgba, rgb565, 3 * sizeof(fb_bitfield)) == 0) {
format = QImage::Format_RGB16;
} else if (memcmp(rgba, bgr565, 3 * sizeof(fb_bitfield)) == 0) {
format = QImage::Format_RGB16;
pixeltype = QScreen::BGRPixel;
}
break;
}
case 15: {
const fb_bitfield rgb1555[4] = {{10, 5, 0}, {5, 5, 0},
{0, 5, 0}, {15, 1, 0}};
const fb_bitfield bgr1555[4] = {{0, 5, 0}, {5, 5, 0},
{10, 5, 0}, {15, 1, 0}};
if (memcmp(rgba, rgb1555, 3 * sizeof(fb_bitfield)) == 0) {
format = QImage::Format_RGB555;
} else if (memcmp(rgba, bgr1555, 3 * sizeof(fb_bitfield)) == 0) {
format = QImage::Format_RGB555;
pixeltype = QScreen::BGRPixel;
}
break;
}
case 12: {
const fb_bitfield rgb444[4] = {{8, 4, 0}, {4, 4, 0},
{0, 4, 0}, {0, 0, 0}};
if (memcmp(rgba, rgb444, 3 * sizeof(fb_bitfield)) == 0)
format = QImage::Format_RGB444;
break;
}
case 8:
break;
case 1:
format = QImage::Format_Mono; //###: LSB???
break;
default:
break;
}
QScreen::setPixelFormat(format);
}
bool QLinuxFbScreen::useOffscreen()
{
// Not done for 8Track because on e-Ink displays,
// everything is offscreen anyway
if (d_ptr->driverType == EInk8Track || ((mapsize - size) < 16*1024))
return false;
return true;
}
QT_END_NAMESPACE
#endif // QT_NO_QWS_LINUXFB