plan9fox/sys/src/9/pc/screen.c
cinap_lenrek d48a089486 pc64: implement simple write combining for framebuffers with the PAT
on some modern machines like the x250, the bios arranges the mtrr's
and the framebuffer membar in a way that doesnt allow us to mark
the framebuffer pages as write combining, leading to slow graphics.

since the pentium III, the processor interprets the page table bit
combinations of the WT, CD and bit7 bits as an index into the
page attribute table (PAT).

to not change the semantics of the WT and CD bits, we preserve
the bit patterns 0-3 and use the last entry 7 for write combining.
(done in mmuinit() for each core).

the new patwc() function takes virtual address range and changes
the page table marking the range as write combining. no attempt
is made on invalidating tlb's. doesnt matter in our case as the
following mtrr() call in screen.c does it for us.
2016-12-15 23:27:01 +01:00

693 lines
12 KiB
C

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "ureg.h"
#include "../port/error.h"
#define Image IMAGE
#include <draw.h>
#include <memdraw.h>
#include <cursor.h>
#include "screen.h"
Rectangle physgscreenr;
Memimage *gscreen;
VGAscr vgascreen[1];
int
screensize(int x, int y, int, ulong chan)
{
VGAscr *scr;
qlock(&drawlock);
if(waserror()){
qunlock(&drawlock);
nexterror();
}
if(memimageinit() < 0)
error("memimageinit failed");
lock(&vgascreenlock);
if(waserror()){
unlock(&vgascreenlock);
nexterror();
}
scr = &vgascreen[0];
scr->gscreendata = nil;
scr->gscreen = nil;
if(gscreen){
freememimage(gscreen);
gscreen = nil;
}
if(scr->paddr == 0){
if(scr->dev && scr->dev->page){
scr->vaddr = KADDR(VGAMEM());
scr->apsize = 1<<16;
}
scr->softscreen = 1;
}
if(scr->softscreen){
gscreen = allocmemimage(Rect(0,0,x,y), chan);
scr->useflush = 1;
}else{
static Memdata md;
md.ref = 1;
if((md.bdata = scr->vaddr) == 0)
error("framebuffer not maped");
gscreen = allocmemimaged(Rect(0,0,x,y), chan, &md);
scr->useflush = scr->dev && scr->dev->flush;
}
if(gscreen == nil)
error("no memory for vga memimage");
scr->palettedepth = 6; /* default */
scr->memdefont = getmemdefont();
scr->gscreen = gscreen;
scr->gscreendata = gscreen->data;
physgscreenr = gscreen->r;
vgaimageinit(chan);
unlock(&vgascreenlock);
poperror();
drawcmap();
swcursorinit();
qunlock(&drawlock);
poperror();
return 0;
}
int
screenaperture(int size, int align)
{
VGAscr *scr;
scr = &vgascreen[0];
if(scr->paddr) /* set up during enable */
return 0;
if(size == 0)
return 0;
if(scr->dev && scr->dev->linear){
scr->dev->linear(scr, size, align);
return 0;
}
/*
* Need to allocate some physical address space.
* The driver will tell the card to use it.
*/
size = PGROUND(size);
scr->paddr = upaalloc(size, align);
if(scr->paddr == 0)
return -1;
scr->vaddr = vmap(scr->paddr, size);
if(scr->vaddr == nil)
return -1;
scr->apsize = size;
return 0;
}
uchar*
attachscreen(Rectangle* r, ulong* chan, int* d, int* width, int *softscreen)
{
VGAscr *scr;
scr = &vgascreen[0];
if(scr->gscreen == nil || scr->gscreendata == nil)
return nil;
*r = scr->gscreen->clipr;
*chan = scr->gscreen->chan;
*d = scr->gscreen->depth;
*width = scr->gscreen->width;
if(scr->gscreendata->allocd){
/*
* we use a memimage as softscreen. devdraw will create its own
* screen image on the backing store of that image. when our gscreen
* and devdraws screenimage gets freed, the imagedata will
* be released.
*/
*softscreen = 0xa110c;
scr->gscreendata->ref++;
} else
*softscreen = scr->useflush ? 1 : 0;
return scr->gscreendata->bdata;
}
void
flushmemscreen(Rectangle r)
{
VGAscr *scr;
uchar *sp, *disp, *sdisp, *edisp;
int y, len, incs, off, page;
scr = &vgascreen[0];
if(scr->gscreen == nil || scr->useflush == 0)
return;
if(rectclip(&r, scr->gscreen->r) == 0)
return;
if(scr->dev && scr->dev->flush){
scr->dev->flush(scr, r);
return;
}
disp = scr->vaddr;
incs = scr->gscreen->width*sizeof(ulong);
off = (r.min.x*scr->gscreen->depth) / 8;
len = (r.max.x*scr->gscreen->depth + 7) / 8;
len -= off;
off += r.min.y*incs;
sp = scr->gscreendata->bdata + scr->gscreen->zero + off;
/*
* Linear framebuffer with softscreen.
*/
if(scr->paddr){
sdisp = disp+off;
for(y = r.min.y; y < r.max.y; y++) {
memmove(sdisp, sp, len);
sp += incs;
sdisp += incs;
}
return;
}
/*
* Paged framebuffer window.
*/
if(scr->dev == nil || scr->dev->page == nil)
return;
page = off/scr->apsize;
off %= scr->apsize;
sdisp = disp+off;
edisp = disp+scr->apsize;
scr->dev->page(scr, page);
for(y = r.min.y; y < r.max.y; y++) {
if(sdisp + incs < edisp) {
memmove(sdisp, sp, len);
sp += incs;
sdisp += incs;
}
else {
off = edisp - sdisp;
page++;
if(off <= len){
if(off > 0)
memmove(sdisp, sp, off);
scr->dev->page(scr, page);
if(len - off > 0)
memmove(disp, sp+off, len - off);
}
else {
memmove(sdisp, sp, len);
scr->dev->page(scr, page);
}
sp += incs;
sdisp += incs - scr->apsize;
}
}
}
void
getcolor(ulong p, ulong* pr, ulong* pg, ulong* pb)
{
VGAscr *scr;
ulong x;
scr = &vgascreen[0];
if(scr->gscreen == nil)
return;
switch(scr->gscreen->depth){
default:
x = 0x0F;
break;
case 8:
x = 0xFF;
break;
}
p &= x;
lock(&cursor);
*pr = scr->colormap[p][0];
*pg = scr->colormap[p][1];
*pb = scr->colormap[p][2];
unlock(&cursor);
}
int
setpalette(ulong p, ulong r, ulong g, ulong b)
{
VGAscr *scr;
int d;
scr = &vgascreen[0];
d = scr->palettedepth;
lock(&cursor);
scr->colormap[p][0] = r;
scr->colormap[p][1] = g;
scr->colormap[p][2] = b;
vgao(PaddrW, p);
vgao(Pdata, r>>(32-d));
vgao(Pdata, g>>(32-d));
vgao(Pdata, b>>(32-d));
unlock(&cursor);
return ~0;
}
/*
* On some video cards (e.g. Mach64), the palette is used as the
* DAC registers for >8-bit modes. We don't want to set them when the user
* is trying to set a colormap and the card is in one of these modes.
*/
int
setcolor(ulong p, ulong r, ulong g, ulong b)
{
VGAscr *scr;
int x;
scr = &vgascreen[0];
if(scr->gscreen == nil)
return 0;
switch(scr->gscreen->depth){
case 1:
case 2:
case 4:
x = 0x0F;
break;
case 8:
x = 0xFF;
break;
default:
return 0;
}
p &= x;
return setpalette(p, r, g, b);
}
void
swenable(VGAscr*)
{
swcursorload(&arrow);
}
void
swdisable(VGAscr*)
{
}
void
swload(VGAscr*, Cursor *curs)
{
swcursorload(curs);
}
int
swmove(VGAscr*, Point p)
{
swcursorhide();
swcursordraw(p);
return 0;
}
VGAcur swcursor =
{
"soft",
swenable,
swdisable,
swload,
swmove,
};
void
cursoron(void)
{
VGAscr *scr;
VGAcur *cur;
scr = &vgascreen[0];
cur = scr->cur;
if(cur == nil || cur->move == nil)
return;
if(cur == &swcursor)
qlock(&drawlock);
lock(&cursor);
cur->move(scr, mousexy());
unlock(&cursor);
if(cur == &swcursor)
qunlock(&drawlock);
}
void
cursoroff(void)
{
}
void
setcursor(Cursor* curs)
{
VGAscr *scr;
scr = &vgascreen[0];
if(scr->cur == nil || scr->cur->load == nil)
return;
scr->cur->load(scr, curs);
}
int hwaccel = 0;
int hwblank = 0;
int panning = 0;
int
hwdraw(Memdrawparam *par)
{
VGAscr *scr;
Memimage *dst, *src, *mask;
Memdata *scrd;
int m;
scr = &vgascreen[0];
scrd = scr->gscreendata;
if(scr->gscreen == nil || scrd == nil)
return 0;
if((dst = par->dst) == nil || dst->data == nil)
return 0;
if((src = par->src) && src->data == nil)
src = nil;
if((mask = par->mask) && mask->data == nil)
mask = nil;
if(scr->cur == &swcursor){
if(dst->data->bdata == scrd->bdata)
swcursoravoid(par->r);
if(src && src->data->bdata == scrd->bdata)
swcursoravoid(par->sr);
if(mask && mask->data->bdata == scrd->bdata)
swcursoravoid(par->mr);
}
if(!hwaccel || scr->softscreen)
return 0;
if(dst->data->bdata != scrd->bdata || src == nil || mask == nil)
return 0;
/*
* If we have an opaque mask and source is one opaque
* pixel we can convert to the destination format and just
* replicate with memset.
*/
m = Simplesrc|Simplemask|Fullmask;
if(scr->fill
&& (par->state&m)==m
&& ((par->srgba&0xFF) == 0xFF)
&& (par->op&S) == S)
return scr->fill(scr, par->r, par->sdval);
/*
* If no source alpha, an opaque mask, we can just copy the
* source onto the destination. If the channels are the same and
* the source is not replicated, memmove suffices.
*/
m = Simplemask|Fullmask;
if(scr->scroll
&& src->data->bdata==dst->data->bdata
&& !(src->flags&Falpha)
&& (par->state&m)==m
&& (par->op&S) == S)
return scr->scroll(scr, par->r, par->sr);
return 0;
}
void
blankscreen(int blank)
{
VGAscr *scr;
scr = &vgascreen[0];
if(hwblank){
if(scr->blank)
scr->blank(scr, blank);
else
vgablank(scr, blank);
}
}
static char*
vgalinearaddr0(VGAscr *scr, ulong paddr, int size)
{
int x, nsize;
ulong npaddr;
/*
* new approach. instead of trying to resize this
* later, let's assume that we can just allocate the
* entire window to start with.
*/
if(scr->paddr == paddr && size <= scr->apsize)
return nil;
if(scr->paddr){
/*
* could call vunmap and vmap,
* but worried about dangling pointers in devdraw
*/
return "cannot grow vga frame buffer";
}
/* round to page boundary, just in case */
x = paddr&(BY2PG-1);
npaddr = paddr-x;
nsize = PGROUND(size+x);
/*
* Don't bother trying to map more than 4000x4000x32 = 64MB.
* We only have a 256MB window.
*/
if(nsize > 64*MB)
nsize = 64*MB;
scr->vaddr = vmap(npaddr, nsize);
if(scr->vaddr == 0)
return "cannot allocate vga frame buffer";
patwc(scr->vaddr, nsize);
scr->vaddr = (char*)scr->vaddr+x;
scr->paddr = paddr;
scr->apsize = nsize;
mtrr(npaddr, nsize, "wc");
return nil;
}
static char*
vgalinearpci0(VGAscr *scr)
{
ulong paddr;
int i, size, best;
Pcidev *p;
p = scr->pci;
/*
* Scan for largest memory region on card.
* Some S3 cards (e.g. Savage) have enormous
* mmio regions (but even larger frame buffers).
* Some 3dfx cards (e.g., Voodoo3) have mmio
* buffers the same size as the frame buffer,
* but only the frame buffer is marked as
* prefetchable (bar&8). If a card doesn't fit
* into these heuristics, its driver will have to
* call vgalinearaddr directly.
*/
best = -1;
for(i=0; i<nelem(p->mem); i++){
if(p->mem[i].bar&1) /* not memory */
continue;
if(p->mem[i].size < 640*480) /* not big enough */
continue;
if(best==-1
|| p->mem[i].size > p->mem[best].size
|| (p->mem[i].size == p->mem[best].size
&& (p->mem[i].bar&8)
&& !(p->mem[best].bar&8)))
best = i;
}
if(best >= 0){
paddr = p->mem[best].bar & ~0x0F;
size = p->mem[best].size;
return vgalinearaddr0(scr, paddr, size);
}
return "no video memory found on pci card";
}
void
vgalinearpci(VGAscr *scr)
{
char *err;
if(scr->pci == nil)
return;
if((err = vgalinearpci0(scr)) != nil)
error(err);
}
void
vgalinearaddr(VGAscr *scr, ulong paddr, int size)
{
char *err;
if((err = vgalinearaddr0(scr, paddr, size)) != nil)
error(err);
}
static char*
bootmapfb(VGAscr *scr, ulong pa, ulong sz)
{
ulong start, end;
Pcidev *p;
int i;
for(p = pcimatch(nil, 0, 0); p != nil; p = pcimatch(p, 0, 0)){
for(i=0; i<nelem(p->mem); i++){
if(p->mem[i].bar & 1)
continue;
start = p->mem[i].bar & ~0xF;
end = start + p->mem[i].size;
if(pa == start && (pa + sz) <= end){
scr->pci = p;
return vgalinearpci0(scr);
}
}
}
return vgalinearaddr0(scr, pa, sz);
}
/*
* called early on boot to attach to framebuffer
* setup by bootloader/firmware or plan9.
*/
void
bootscreeninit(void)
{
static Memdata md;
VGAscr *scr;
int x, y, z;
ulong chan, pa, sz;
char *s, *p, *err;
/* *bootscreen=WIDTHxHEIGHTxDEPTH CHAN PA [SZ] */
s = getconf("*bootscreen");
if(s == nil)
return;
x = strtoul(s, &s, 0);
if(x == 0 || *s++ != 'x')
return;
y = strtoul(s, &s, 0);
if(y == 0 || *s++ != 'x')
return;
z = strtoul(s, &s, 0);
if(*s != ' ')
return;
if((p = strchr(++s, ' ')) == nil)
return;
*p = 0;
chan = strtochan(s);
*p = ' ';
if(chan == 0 || chantodepth(chan) != z)
return;
sz = 0;
pa = strtoul(p+1, &s, 0);
if(pa == 0)
return;
if(*s++ == ' ')
sz = strtoul(s, nil, 0);
if(sz < x * y * (z+7)/8)
sz = x * y * (z+7)/8;
/* map framebuffer */
scr = &vgascreen[0];
if((err = bootmapfb(scr, pa, sz)) != nil){
print("bootmapfb: %s\n", err);
return;
}
if(memimageinit() < 0)
return;
md.ref = 1;
md.bdata = scr->vaddr;
gscreen = allocmemimaged(Rect(0,0,x,y), chan, &md);
if(gscreen == nil)
return;
scr->palettedepth = 6; /* default */
scr->memdefont = getmemdefont();
scr->gscreen = gscreen;
scr->gscreendata = gscreen->data;
scr->softscreen = 0;
scr->useflush = 0;
scr->dev = nil;
physgscreenr = gscreen->r;
vgaimageinit(chan);
vgascreenwin(scr);
/* turn mouse cursor on */
swcursorinit();
scr->cur = &swcursor;
scr->cur->enable(scr);
cursoron();
conf.monitor = 1;
}
/*
* called from devvga when the framebuffer is setup
* to set *bootscreen= that can be passed on to a
* new kernel on reboot.
*/
void
bootscreenconf(VGAscr *scr)
{
char conf[100], chan[30];
conf[0] = '\0';
if(scr != nil && scr->paddr != 0)
snprint(conf, sizeof(conf), "%dx%dx%d %s %#p %d\n",
scr->gscreen->r.max.x, scr->gscreen->r.max.y,
scr->gscreen->depth, chantostr(chan, scr->gscreen->chan),
scr->paddr, scr->apsize);
ksetenv("*bootscreen", conf, 1);
}