ref: 9ced712a2ad70557a8ceb89ff3b969b92c85f68c
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\n", p); return -1; } if(p[5] < 2){ werrstr("invalid vesa version: %.4H\n", 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; } } } }