ref: 35c16dfd5ecc3ec995f0fca1e9badea2f79cfa57
dir: /sys/src/cmd/aux/vga/vesa.c/
#include <u.h>
#include <libc.h>
#include <bio.h>
#include </386/include/ureg.h>
typedef struct Ureg Ureg;
#include "pci.h"
#include "vga.h"
typedef struct Vbe Vbe;
typedef struct Vmode Vmode;
enum
{
MemSize = 1024*1024,
PageSize = 4096,
RealModeBuf = 0x9000,
};
struct Vbe
{
int rmfd; /* /dev/realmode */
int memfd; /* /dev/realmem */
uchar *mem; /* copy of memory; 1MB */
uchar *isvalid; /* 1byte per 4kB in mem */
uchar *modebuf;
};
struct Vmode
{
char size[Namelen+1];
char chan[Namelen+1];
int id;
int attr; /* flags */
int bpl;
int dx, dy;
int depth;
char *model;
int directcolor; /* flags */
ulong paddr;
};
#define WORD(p) ((p)[0] | ((p)[1]<<8))
#define LONG(p) ((p)[0] | ((p)[1]<<8) | ((p)[2]<<16) | ((p)[3]<<24))
#define PWORD(p, v) (p)[0] = (v); (p)[1] = (v)>>8
#define PLONG(p, v) (p)[0] = (v); (p)[1] = (v)>>8; (p)[2] = (v)>>16; (p)[3] = (v)>>24
static Vbe *vbe;
static int dspcon; /* connected displays bitmask */
static int dspact; /* active displays bitmask */
static int (*setscale)(Vbe*, char*);
Vbe *mkvbe(void);
int vbecheck(Vbe*);
uchar *vbemodes(Vbe*);
int vbemodeinfo(Vbe*, int, Vmode*);
int vbegetmode(Vbe*);
int vbesetmode(Vbe*, int);
void vbeprintinfo(Vbe*);
void vbeprintmodeinfo(Vbe*, int, char*);
int vbesnarf(Vbe*, Vga*);
void vesaddc(void);
Edid* vbeddcedid(Vbe *vbe);
uchar* vbesetup(Vbe*, Ureg*, int);
int vbecall(Vbe*, Ureg*);
int setdisplay(Vbe *vbe, int display);
int getdisplay(Vbe*);
void fixbios(Vbe*);
int
dbvesa(Vga* vga)
{
if(vbe == nil){
vbe = mkvbe();
if(vbe == nil){
fprint(2, "mkvbe: %r\n");
return 0;
}
}
if(vbecheck(vbe) < 0){
fprint(2, "dbvesa: %r\n");
return 0;
}
vga->link = alloc(sizeof(Ctlr));
*vga->link = vesa;
vga->vesa = vga->link;
vga->ctlr = vga->link;
vga->link->link = alloc(sizeof(Ctlr));
*vga->link->link = softhwgc;
vga->hwgc = vga->link->link;
return 1;
}
static char*
cracksize(char *size, char **scale, int *display)
{
static char buf[256];
char *f[4];
int i, n;
*scale = nil;
*display = 0;
snprint(buf, sizeof(buf), "%s", size);
n = getfields(buf, f, nelem(f), 0, ",");
for(i=1; i<n; i++){
if(f[i][0] == '#')
*display = atoi(&f[i][1]);
else if(strncmp(f[i], "scale", 5) == 0)
*scale = f[i];
else
error("bad size option: %s for %s\n", f[i], f[0]);
}
return f[0];
}
static char*
rgbmask2chan(char *buf, int depth, u32int rm, u32int gm, u32int bm)
{
u32int m[4], dm; /* r,g,b,x */
char tmp[32];
int c, n;
dm = 1<<depth-1;
dm |= dm-1;
m[0] = rm & dm;
m[1] = gm & dm;
m[2] = bm & dm;
m[3] = (~(m[0] | m[1] | m[2])) & dm;
buf[0] = 0;
Next:
for(c=0; c<4; c++){
for(n = 0; m[c] & (1<<n); n++)
;
if(n){
m[0] >>= n, m[1] >>= n, m[2] >>= n, m[3] >>= n;
snprint(tmp, sizeof tmp, "%c%d%s", "rgbx"[c], n, buf);
strcpy(buf, tmp);
goto Next;
}
}
return buf;
}
Mode*
dbvesamode(Vga *vga, char *size)
{
int i, width, display;
int oldmode, olddisplay;
uchar *p, *ep;
Vmode vm;
Mode *m;
Modelist *l;
char *scale;
if(vbe == nil)
return nil;
size = cracksize(size, &scale, &display);
oldmode = olddisplay = 0;
if(display != 0){
olddisplay = getdisplay(vbe);
oldmode = vbegetmode(vbe);
if(setdisplay(vbe, display) < 0)
return nil;
}
if(strncmp(size, "0x", 2) == 0){
if(vbemodeinfo(vbe, strtol(size+2, nil, 16), &vm) == 0)
goto havemode;
}else{
if(p = vbemodes(vbe)){
for(ep=p+1024; (p[0]!=0xFF || p[1]!=0xFF) && p<ep; p+=2){
if(vbemodeinfo(vbe, WORD(p), &vm) < 0)
continue;
if(strcmp(vm.size, size) == 0)
goto havemode;
}
}
if(1){
fprint(2, "warning: scanning for unoffered vesa modes\n");
for(i=0x100; i<0x200; i++){
if(vbemodeinfo(vbe, i, &vm) < 0)
continue;
if(strcmp(vm.size, size) == 0)
goto havemode;
}
}
}
if(display != 0 && setdisplay(vbe, olddisplay) == 0)
vbesetmode(vbe, oldmode);
werrstr("no such vesa mode");
return nil;
havemode:
if(display != 0 && setdisplay(vbe, olddisplay) == 0)
vbesetmode(vbe, oldmode);
m = alloc(sizeof(Mode));
m->x = vm.dx;
m->y = vm.dy;
m->ht = m->x;
m->shb = m->x;
m->ehb = m->x;
m->shs = m->x;
m->ehs = m->x;
m->vt = m->y;
m->vrs = m->y;
m->vre = m->y;
m->frequency = m->ht * m->vt * 60;
/* get default monitor timing */
for(i=0; vesamodes[i]; i++){
if(vesamodes[i]->x != vm.dx || vesamodes[i]->y != vm.dy)
continue;
*m = *vesamodes[i];
break;
}
for(i=0; i<nelem(vga->edid); i++){
if(vga->edid[i] == nil)
continue;
for(l = vga->edid[i]->modelist; l; l = l->next){
if(l->x != vm.dx || l->y != vm.dy)
continue;
*m = *((Mode*)l);
break;
}
}
strcpy(m->type, "vesa");
strcpy(m->size, vm.size);
strcpy(m->chan, vm.chan);
m->z = vm.depth;
/* account for framebuffer stride */
width = vm.bpl * 8 / m->z;
if(width > m->x)
m->attr = mkattr(m->attr, "virtx", "%d", width);
if(scale != nil)
m->attr = mkattr(m->attr, "scale", "%s", scale);
if(display != 0)
m->attr = mkattr(m->attr, "display", "%d", display);
m->attr = mkattr(m->attr, "id", "0x%x", vm.id);
return m;
}
static void
snarf(Vga* vga, Ctlr* ctlr)
{
if(vbe == nil)
vbe = mkvbe();
if(vbe != nil)
vga->vesa = ctlr;
vbesnarf(vbe, vga);
vga->linear = 1;
ctlr->flag |= Hlinear|Ulinear|Fsnarf;
}
static void
options(Vga *vga, Ctlr *ctlr)
{
char *v;
if(v = dbattr(vga->mode->attr, "virtx")){
vga->virtx = atoi(v);
vga->virty = vga->mode->y;
}
ctlr->flag |= Foptions;
}
static void
load(Vga* vga, Ctlr* ctlr)
{
int mode, display;
int oldmode, olddisplay;
char *ds, *scale;
if(vbe == nil)
error("no vesa bios\n");
mode = strtol(dbattr(vga->mode->attr, "id"), nil, 0);
scale = dbattr(vga->mode->attr, "scale");
ds = dbattr(vga->mode->attr, "display");
display = ds == nil ? 0 : atoi(ds);
olddisplay = oldmode = 0;
/* need to reset scaling before switching displays */
if(setscale != nil)
(*setscale)(vbe, "scalefull");
if(display != 0){
olddisplay = getdisplay(vbe);
oldmode = vbegetmode(vbe);
if(setdisplay(vbe, display) < 0){
fprint(2, "setdisplay: %r\n");
ctlr->flag |= Ferror;
return;
}
}
if(vbesetmode(vbe, mode) < 0){
fprint(2, "vbesetmode: %r\n");
ctlr->flag |= Ferror;
if(display != 0){
setdisplay(vbe, olddisplay);
vbesetmode(vbe, oldmode);
}
return;
}
if(setscale != nil){
if((*setscale)(vbe, scale) < 0){
fprint(2, "setscale: %r\n");
}
}
}
static void
dump(Vga *, Ctlr*)
{
int i;
char did[0x200];
uchar *p, *ep;
if(vbe == nil){
Bprint(&stdout, "no vesa bios\n");
return;
}
memset(did, 0, sizeof did);
vbeprintinfo(vbe);
p = vbemodes(vbe);
if(p != nil){
for(ep=p+1024; (p[0]!=0xFF || p[1]!=0xFF) && p<ep; p+=2){
vbeprintmodeinfo(vbe, WORD(p), "");
if(WORD(p) < nelem(did))
did[WORD(p)] = 1;
}
}
for(i=0x100; i<0x1FF; i++)
if(!did[i])
vbeprintmodeinfo(vbe, i, " (unoffered)");
}
static int
intelscale(Vbe *vbe, char *scale)
{
Ureg u;
int cx;
if(scale == nil)
cx = 0;
else if(strcmp(scale, "scalefull") == 0)
cx = 4;
else {
werrstr("intelscale: not supported: %s", scale);
return -1;
}
vbesetup(vbe, &u, 0x5F61);
u.bx = 0;
u.cx = cx; /* horizontal */
u.dx = cx; /* vertical */
return vbecall(vbe, &u);
}
static int
nvidiascale(Vbe *vbe, char *scale)
{
Ureg u;
int cx;
if(scale == nil)
cx = 1;
else if(strcmp(scale, "scaleaspect") == 0)
cx = 3;
else if(strcmp(scale, "scalefull") == 0)
cx = 0;
else {
werrstr("nvidiascale: not supported: %s", scale);
return -1;
}
vbesetup(vbe, &u, 0x4F14);
u.bx = 0x102;
u.cx = cx;
return vbecall(vbe, &u);
}
Ctlr vesa = {
"vesa",
snarf,
options,
nil,
load,
dump,
};
Ctlr softhwgc = {
"soft",
};
/*
* VESA bios extension
*/
static Flag capabilityflag[] = {
0x01, "8-bit-dac",
0x02, "not-vga",
0x04, "ramdac-needs-blank",
0x08, "stereoscopic",
0x10, "stereo-evc",
0
};
enum {
AttrSupported = 1<<0,
AttrTTY = 1<<2,
AttrColor = 1<<3,
AttrGraphics = 1<<4,
AttrNotVGA = 1<<5,
AttrNotWinVGA = 1<<6,
AttrLinear = 1<<7,
AttrDoublescan = 1<<8,
AttrInterlace = 1<<9,
AttrTriplebuf = 1<<10,
AttrStereo = 1<<11,
AttrDualAddr = 1<<12,
};
static Flag modeattributesflags[] = {
AttrSupported, "supported",
AttrTTY, "tty",
AttrColor, "color",
AttrGraphics, "graphics",
AttrNotVGA, "not-vga",
AttrNotWinVGA, "no-windowed-vga",
AttrLinear, "linear",
AttrDoublescan, "double-scan",
AttrInterlace, "interlace",
AttrTriplebuf, "triple-buffer",
AttrStereo, "stereoscopic",
AttrDualAddr, "dual-start-addr",
0
};
static Flag winattributesflags[] = {
1<<0, "relocatable",
1<<1, "readable",
1<<2, "writeable",
0
};
static Flag directcolorflags[] = {
1<<0, "programmable-color-ramp",
1<<1, "x-usable",
0
};
enum {
ModText = 0,
ModCGA,
ModHercules,
ModPlanar,
ModPacked,
ModNonChain4,
ModDirect,
ModYUV,
};
static char *modelstr[] = {
[ModText] "text",
[ModCGA] "cga",
[ModHercules] "hercules",
[ModPlanar] "planar",
[ModPacked] "packed",
[ModNonChain4] "non-chain4",
[ModDirect] "direct",
[ModYUV] "YUV",
};
Vbe*
mkvbe(void)
{
Vbe *vbe;
vbe = alloc(sizeof(Vbe));
if((vbe->rmfd = open("/dev/realmode", ORDWR)) < 0)
return nil;
if((vbe->memfd = open("/dev/realmodemem", ORDWR)) < 0)
return nil;
vbe->mem = alloc(MemSize);
vbe->isvalid = alloc(MemSize/PageSize);
vbe->modebuf = alloc(PageSize);
fixbios(vbe);
return vbe;
}
static void
loadpage(Vbe *vbe, int p, int wr)
{
if(p >= MemSize/PageSize)
return;
if(vbe->isvalid[p] == 0)
if(pread(vbe->memfd, vbe->mem+p*PageSize, PageSize, p*PageSize) != PageSize)
error("read /dev/realmodemem: %r\n");
vbe->isvalid[p] = 1+wr;
}
static void
flushpage(Vbe *vbe, int p)
{
if(p >= MemSize/PageSize || vbe->isvalid[p]!=2)
return;
if(pwrite(vbe->memfd, vbe->mem+p*PageSize, PageSize, p*PageSize) != PageSize)
error("write /dev/realmodemem: %r\n");
vbe->isvalid[p] = 1;
}
static void*
getmem(Vbe *vbe, int off, int wr)
{
if(off == 0 || off >= MemSize)
return nil;
loadpage(vbe, off/PageSize, wr);
if(off % PageSize)
loadpage(vbe, (off/PageSize)+1, wr);
return vbe->mem+off;
}
static void*
unfarptr(Vbe *vbe, uchar *p)
{
int seg, off;
seg = WORD(p+2);
off = WORD(p);
off += seg<<4;
return getmem(vbe, off, 0);
}
uchar*
vbesetup(Vbe *vbe, Ureg *u, int ax)
{
uchar *p;
memset(u, 0, sizeof *u);
u->ax = ax;
u->es = (RealModeBuf>>4)&0xF000;
u->di = RealModeBuf&0xFFFF;
p = getmem(vbe, RealModeBuf, 1);
memset(p, 0, PageSize);
return p;
}
void
vbeflush(Vbe *vbe)
{
int p;
for(p=0; p<MemSize/PageSize; p++)
flushpage(vbe, p);
memset(vbe->isvalid, 0, MemSize/PageSize);
}
int
vbecall(Vbe *vbe, Ureg *u)
{
int ax;
ax = u->ax >> 8;
u->trap = 0x10;
vbeflush(vbe);
if(pwrite(vbe->rmfd, u, sizeof *u, 0) != sizeof *u)
error("write /dev/realmode: %r\n");
if(pread(vbe->rmfd, u, sizeof *u, 0) != sizeof *u)
error("read /dev/realmode: %r\n");
getmem(vbe, RealModeBuf, 0);
if((u->ax&0xFFFF) != ax){
werrstr("VBE error %#.4lux", u->ax&0xFFFF);
return -1;
}
return 0;
}
int
vbecheck(Vbe *vbe)
{
uchar *p;
Ureg u;
p = vbesetup(vbe, &u, 0x4F00);
strcpy((char*)p, "VBE2");
if(vbecall(vbe, &u) < 0)
return -1;
if(memcmp(p, "VESA", 4)){
werrstr("invalid vesa signature %.4H", p);
return -1;
}
if(p[5] < 2){
werrstr("invalid vesa version: %.4H", p+4);
return -1;
}
return 0;
}
int
vbesnarf(Vbe *vbe, Vga *vga)
{
char *oem;
uchar *p;
Ureg u;
p = vbesetup(vbe, &u, 0x4F00);
strcpy((char*)p, "VBE2");
if(vbecall(vbe, &u) < 0)
return -1;
if(memcmp(p, "VESA", 4) != 0 || p[5] < 2)
return -1;
vga->apz = WORD(p+18)*0x10000UL;
oem = unfarptr(vbe, p+6);
if(strncmp(oem, "Intel", 5) == 0){
setscale = intelscale;
/* detect connected display devices */
vbesetup(vbe, &u, 0x5F64);
u.bx = 0x200;
if(vbecall(vbe, &u) < 0)
u.cx = 0;
dspcon = (u.cx >> 8) & 0xf;
/* detect active display devices */
vbesetup(vbe, &u, 0x5F64);
u.bx = 0x100;
if(vbecall(vbe, &u) < 0)
u.cx = 0;
dspact = u.cx;
}
/* breaks modeset on 10de/0392 "G73 [GeForce 7600 GS]" -- cinap
else if(memcmp(oem, "NVIDIA", 6) == 0)
setscale = nvidiascale;
*/
vga->edid[0] = vbeddcedid(vbe);
return 0;
}
void
vbeprintinfo(Vbe *vbe)
{
uchar *p;
Ureg u;
int i;
p = vbesetup(vbe, &u, 0x4F00);
strcpy((char*)p, "VBE2");
if(vbecall(vbe, &u) < 0)
return;
printitem("vesa", "sig");
Bprint(&stdout, "%.4s %d.%d\n", (char*)p, p[5], p[4]);
if(p[5] < 2)
return;
printitem("vesa", "oem");
Bprint(&stdout, "%s %d.%d\n", unfarptr(vbe, p+6), p[21], p[20]);
printitem("vesa", "vendor");
Bprint(&stdout, "%s\n", unfarptr(vbe, p+22));
printitem("vesa", "product");
Bprint(&stdout, "%s\n", unfarptr(vbe, p+26));
printitem("vesa", "rev");
Bprint(&stdout, "%s\n", unfarptr(vbe, p+30));
printitem("vesa", "cap");
printflags(capabilityflag, p[10]);
printitem("vesa", "mem");
Bprint(&stdout, "%lud\n", WORD(p+18)*0x10000UL);
if(dspcon != 0){
printitem("vesa", "dsp con");
for(i = 0; i < 8; i++)
if(dspcon & (1<<i))
Bprint(&stdout, "%d ", i+1);
Bprint(&stdout, "\n");
}
if(dspact != 0){
printitem("vesa", "dsp act");
for(i = 0; i < 8; i++)
if(dspact & (1<<i))
Bprint(&stdout, "%d ", i+1);
Bprint(&stdout, "\n");
}
}
uchar*
vbemodes(Vbe *vbe)
{
uchar *p;
Ureg u;
p = vbesetup(vbe, &u, 0x4F00);
strcpy((char*)p, "VBE2");
if(vbecall(vbe, &u) < 0)
return nil;
memmove(vbe->modebuf, unfarptr(vbe, p+14), 1024);
return vbe->modebuf;
}
int
vbemodeinfo(Vbe *vbe, int id, Vmode *m)
{
uchar *p;
Ureg u;
int mod;
p = vbesetup(vbe, &u, 0x4F01);
u.cx = id;
if(vbecall(vbe, &u) < 0)
return -1;
m->id = id;
m->attr = WORD(p);
if(!(m->attr & AttrSupported))
goto Unsupported;
if(!(m->attr & AttrGraphics))
goto Unsupported;
if(!(m->attr & AttrLinear))
goto Unsupported;
m->bpl = WORD(p+16);
m->dx = WORD(p+18);
m->dy = WORD(p+20);
m->depth = p[25];
if((m->dx * m->dy * m->depth) <= 0)
goto Unsupported;
mod = p[27];
switch(mod){
default:
Unsupported:
werrstr("mode unsupported");
return -1;
case ModCGA:
case ModHercules:
case ModPacked:
case ModDirect:
m->model = modelstr[mod];
break;
}
m->directcolor = p[39];
m->paddr = LONG(p+40);
snprint(m->size, sizeof m->size, "%dx%dx%d", m->dx, m->dy, m->depth);
if(m->depth <= 8)
snprint(m->chan, sizeof m->chan, "%c%d",
(m->attr & AttrColor) ? 'm' : 'k', m->depth);
else
rgbmask2chan(m->chan, m->depth,
(1UL<<p[31])-1 << p[32],
(1UL<<p[33])-1 << p[34],
(1UL<<p[35])-1 << p[36]);
return 0;
}
void
vbeprintmodeinfo(Vbe *vbe, int id, char *suffix)
{
Vmode m;
if(vbemodeinfo(vbe, id, &m) < 0)
return;
printitem("vesa", "mode");
Bprint(&stdout, "0x%ux %s %s %s%s\n",
m.id, m.size, m.chan, m.model, suffix);
}
int
vbegetmode(Vbe *vbe)
{
Ureg u;
vbesetup(vbe, &u, 0x4F03);
if(vbecall(vbe, &u) < 0)
return 0;
return u.bx;
}
int
vbesetmode(Vbe *vbe, int id)
{
Ureg u;
vbesetup(vbe, &u, 0x4F02);
u.bx = id;
if(id != 3)
u.bx |= 3<<14; /* graphics: use linear, do not clear */
return vbecall(vbe, &u);
}
void
vesatextmode(void)
{
if(vbe == nil){
vbe = mkvbe();
if(vbe == nil)
error("mkvbe: %r\n");
}
if(vbecheck(vbe) < 0)
error("vbecheck: %r\n");
if(vbesetmode(vbe, 3) < 0)
error("vbesetmode: %r\n");
}
Edid*
vbeddcedid(Vbe *vbe)
{
uchar *p;
Ureg u;
p = vbesetup(vbe, &u, 0x4F15);
u.bx = 0x0001;
if(vbecall(vbe, &u) < 0)
return nil;
return parseedid128(p);
}
int
getdisplay(Vbe*)
{
int i;
for(i = 0; i < 8; i++)
if(dspact & 1<<i)
return i+1;
/* fallback to a connected one */
for(i = 0; i < 8; i++)
if(dspcon & 1<<i)
return i+1;
return 0;
}
int
setdisplay(Vbe *vbe, int display)
{
Ureg u;
int cx;
if(display == 0)
return 0;
cx = 1<<(display-1);
if(dspcon & cx){
/* switch to common mode before trying */
vbesetmode(vbe, 3);
vbesetup(vbe, &u, 0x5F64);
u.bx = 0;
u.cx = cx;
return vbecall(vbe, &u);
} else {
werrstr("display #%d not connected", display);
return -1;
}
}
void
fixbios(Vbe *vbe)
{
uchar *p;
int i;
/*
* Intel(r) Cantiga Graphics Chip Accelerated VGA BIOS 1.0 has
* a wrong entry in mode alias table at c000:7921 for mode 0x16b
* (1440x900x32) wrongly replacing it with mode 0x162 (768x480x32).
*/
p = getmem(vbe, 0xc7921, 0);
if(p != nil && p[0] == 0x01 && p[1] == 0xff && p[2] == 0xff){
for(i=1; i<64; i++){
p = getmem(vbe, 0xc7921 + 3*i, 0);
if(p == nil || p[0] == 0xff)
break;
if(p[0] == 0x6b && p[1] == 0x6b && p[2] == 0x62){
p = getmem(vbe, 0xc7921 + 3*i, 1);
p[2] = 0x6b; /* fix */
break;
}
}
}
}