1055 lines
22 KiB
C
1055 lines
22 KiB
C
/*
|
|
* This file is part of the UCB release of Plan 9. It is subject to the license
|
|
* terms in the LICENSE file found in the top-level directory of this
|
|
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
|
|
* part of the UCB release of Plan 9, including this file, may be copied,
|
|
* modified, propagated, or distributed except according to the terms contained
|
|
* in the LICENSE file.
|
|
*/
|
|
|
|
#include <u.h>
|
|
#include <lib9.h>
|
|
#include <bio.h>
|
|
#include <vga.h> // hacks from 386 age
|
|
typedef struct VGAreg VGAreg;
|
|
|
|
#include "pci.h"
|
|
#include "vga.h"
|
|
|
|
typedef struct Vbe Vbe;
|
|
typedef struct Vmode Vmode;
|
|
typedef struct Modelist Modelist;
|
|
typedef struct Edid Edid;
|
|
|
|
enum
|
|
{
|
|
MemSize = 1024*1024,
|
|
PageSize = 4096,
|
|
RealModeBuf = 0x9000,
|
|
};
|
|
|
|
struct Vbe
|
|
{
|
|
int rmfd; /* /dev/realmode */
|
|
int memfd; /* /dev/realmem */
|
|
uint8_t *mem; /* copy of memory; 1MB */
|
|
uint8_t *isvalid; /* 1byte per 4kB in mem */
|
|
uint8_t *buf;
|
|
uint8_t *modebuf;
|
|
};
|
|
|
|
struct Vmode
|
|
{
|
|
char name[32];
|
|
char chan[32];
|
|
int id;
|
|
int attr; /* flags */
|
|
int bpl;
|
|
int dx, dy;
|
|
int depth;
|
|
char *model;
|
|
int r, g, b, x;
|
|
int ro, go, bo, xo;
|
|
int directcolor; /* flags */
|
|
uint32_t paddr;
|
|
};
|
|
|
|
struct Edid {
|
|
char mfr[4]; /* manufacturer */
|
|
char serialstr[16]; /* serial number as string (in extended data) */
|
|
char name[16]; /* monitor name as string (in extended data) */
|
|
uint16_t product; /* product code, 0 = unused */
|
|
uint32_t serial; /* serial number, 0 = unused */
|
|
uint8_t version; /* major version number */
|
|
uint8_t revision; /* minor version number */
|
|
uint8_t mfrweek; /* week of manufacture, 0 = unused */
|
|
int mfryear; /* year of manufacture, 0 = unused */
|
|
uint8_t dxcm; /* horizontal image size in cm. */
|
|
uint8_t dycm; /* vertical image size in cm. */
|
|
int gamma; /* gamma*100 */
|
|
int rrmin; /* minimum vertical refresh rate */
|
|
int rrmax; /* maximum vertical refresh rate */
|
|
int hrmin; /* minimum horizontal refresh rate */
|
|
int hrmax; /* maximum horizontal refresh rate */
|
|
uint32_t pclkmax; /* maximum pixel clock */
|
|
int flags;
|
|
|
|
Modelist *modelist; /* list of supported modes */
|
|
};
|
|
|
|
struct Modelist
|
|
{
|
|
Mode;
|
|
Modelist *next;
|
|
};
|
|
|
|
enum {
|
|
Fdigital = 1<<0, /* is a digital display */
|
|
Fdpmsstandby = 1<<1, /* supports DPMS standby mode */
|
|
Fdpmssuspend = 1<<2, /* supports DPMS suspend mode */
|
|
Fdpmsactiveoff = 1<<3, /* supports DPMS active off mode */
|
|
Fmonochrome = 1<<4, /* is a monochrome display */
|
|
Fgtf = 1<<5, /* supports VESA GTF: see /public/doc/vesa/gtf10.pdf */
|
|
};
|
|
|
|
#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 Edid edid;
|
|
|
|
Vbe *mkvbe(void);
|
|
int vbecheck(Vbe*);
|
|
uint8_t *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);
|
|
int vbeddcedid(Vbe *vbe, Edid *e);
|
|
void printedid(Edid*);
|
|
|
|
int
|
|
dbvesa(Vga* vga)
|
|
{
|
|
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;
|
|
}
|
|
|
|
Mode*
|
|
dbvesamode(char *mode)
|
|
{
|
|
int i;
|
|
uint8_t *p, *ep;
|
|
Vmode vm;
|
|
Mode *m;
|
|
|
|
if(vbe == nil)
|
|
return nil;
|
|
|
|
p = vbemodes(vbe);
|
|
if(p == nil)
|
|
return nil;
|
|
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.name, mode) == 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.name, mode) == 0)
|
|
goto havemode;
|
|
}
|
|
}
|
|
werrstr("no such vesa mode");
|
|
return nil;
|
|
|
|
havemode:
|
|
m = alloc(sizeof(Mode));
|
|
strcpy(m->type, "vesa");
|
|
strcpy(m->size, vm.name);
|
|
strcpy(m->chan, vm.chan);
|
|
m->frequency = 100;
|
|
m->x = vm.dx;
|
|
m->y = vm.dy;
|
|
m->z = vm.depth;
|
|
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->attr = alloc(sizeof(Attr));
|
|
m->attr->attr = "id";
|
|
m->attr->val = alloc(32);
|
|
sprint(m->attr->val, "0x%x", vm.id);
|
|
return m;
|
|
}
|
|
|
|
static void
|
|
snarf(Vga* vga, Ctlr* ctlr)
|
|
{
|
|
if(!vbe)
|
|
vbe = mkvbe();
|
|
if(vbe)
|
|
vga->vesa = ctlr;
|
|
vbesnarf(vbe, vga);
|
|
vga->linear = 1;
|
|
ctlr->flag |= Hlinear|Ulinear;
|
|
}
|
|
|
|
static void
|
|
load(Vga* vga, Ctlr* ctlr)
|
|
{
|
|
if(vbe == nil)
|
|
error("no vesa bios\n");
|
|
if(vbesetmode(vbe, atoi(dbattr(vga->mode->attr, "id"))) < 0){
|
|
ctlr->flag |= Ferror;
|
|
fprint(2, "vbesetmode: %r\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump(Vga* vga, Ctlr* ctlr)
|
|
{
|
|
int i;
|
|
char did[0x200];
|
|
uint8_t *p, *ep;
|
|
|
|
if(!vbe){
|
|
Bprint(&stdout, "no vesa bios\n");
|
|
return;
|
|
}
|
|
|
|
memset(did, 0, sizeof did);
|
|
vbeprintinfo(vbe);
|
|
p = vbemodes(vbe);
|
|
if(p){
|
|
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)");
|
|
|
|
|
|
if(vbeddcedid(vbe, &edid) < 0)
|
|
fprint(2, "warning: reading edid: %r\n");
|
|
else
|
|
printedid(&edid);
|
|
}
|
|
|
|
Ctlr vesa = {
|
|
"vesa", /* name */
|
|
snarf, /* snarf */
|
|
0, /* options */
|
|
0, /* init */
|
|
load, /* load */
|
|
dump, /* dump */
|
|
};
|
|
|
|
Ctlr softhwgc = {
|
|
"soft",
|
|
};
|
|
|
|
/*
|
|
* VESA bios extension
|
|
*/
|
|
|
|
typedef struct Flag Flag;
|
|
struct Flag {
|
|
int bit;
|
|
char *desc;
|
|
};
|
|
|
|
static Flag capabilityflag[] = {
|
|
0x01, "8-bit-dac",
|
|
0x02, "not-vga",
|
|
0x04, "ramdac-needs-blank",
|
|
0x08, "stereoscopic",
|
|
0x10, "stereo-evc",
|
|
0
|
|
};
|
|
|
|
#if 0
|
|
static Flag modeattributesflags[] = {
|
|
1<<0, "supported",
|
|
1<<2, "tty",
|
|
1<<3, "color",
|
|
1<<4, "graphics",
|
|
1<<5, "not-vga",
|
|
1<<6, "no-windowed-vga",
|
|
1<<7, "linear",
|
|
1<<8, "double-scan",
|
|
1<<9, "interlace",
|
|
1<<10, "triple-buffer",
|
|
1<<11, "stereoscopic",
|
|
1<<12, "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
|
|
};
|
|
#endif
|
|
|
|
static char *modelstr[] = {
|
|
"text", "cga", "hercules", "planar", "packed", "non-chain4", "direct", "YUV"
|
|
};
|
|
|
|
static void
|
|
printflags(Flag *f, int b)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; f[i].bit; i++)
|
|
if(f[i].bit & b)
|
|
Bprint(&stdout, " %s", f[i].desc);
|
|
Bprint(&stdout, "\n");
|
|
}
|
|
|
|
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->buf = alloc(PageSize);
|
|
vbe->modebuf = alloc(PageSize);
|
|
return vbe;
|
|
}
|
|
|
|
static void
|
|
loadpage(Vbe *vbe, int p)
|
|
{
|
|
if(p >= MemSize/PageSize || vbe->isvalid[p])
|
|
return;
|
|
if(pread(vbe->memfd, vbe->mem+p*PageSize, PageSize, p*PageSize) != PageSize)
|
|
error("read /dev/realmodemem: %r\n");
|
|
vbe->isvalid[p] = 1;
|
|
}
|
|
|
|
static void*
|
|
unfarptr(Vbe *vbe, uint8_t *p)
|
|
{
|
|
int seg, off;
|
|
|
|
seg = WORD(p+2);
|
|
off = WORD(p);
|
|
if(seg==0 && off==0)
|
|
return nil;
|
|
off += seg<<4;
|
|
if(off >= MemSize)
|
|
return nil;
|
|
loadpage(vbe, off/PageSize);
|
|
loadpage(vbe, off/PageSize+1); /* just in case */
|
|
return vbe->mem+off;
|
|
}
|
|
|
|
uint8_t*
|
|
vbesetup(Vbe *vbe, VGAreg *u, int ax)
|
|
{
|
|
memset(vbe->buf, 0, PageSize);
|
|
memset(u, 0, sizeof *u);
|
|
u->ax = ax;
|
|
u->es = (RealModeBuf>>4)&0xF000;
|
|
u->di = RealModeBuf&0xFFFF;
|
|
return vbe->buf;
|
|
}
|
|
|
|
int
|
|
vbecall(Vbe *vbe, VGAreg *u)
|
|
{
|
|
u->trap = 0x10;
|
|
if(pwrite(vbe->memfd, vbe->buf, PageSize, RealModeBuf) != PageSize)
|
|
error("write /dev/realmodemem: %r\n");
|
|
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");
|
|
if(pread(vbe->memfd, vbe->buf, PageSize, RealModeBuf) != PageSize)
|
|
error("read /dev/realmodemem: %r\n");
|
|
if((u->ax&0xFFFF) != 0x004F){
|
|
werrstr("VBE error %#.4lux", u->ax&0xFFFF);
|
|
return -1;
|
|
}
|
|
memset(vbe->isvalid, 0, MemSize/PageSize);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
vbecheck(Vbe *vbe)
|
|
{
|
|
uint8_t *p;
|
|
VGAreg 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){
|
|
werrstr("invalid vesa signature %.4H %.4H\n", p, p+4);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
vbesnarf(Vbe *vbe, Vga *vga)
|
|
{
|
|
uint8_t *p;
|
|
VGAreg 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;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
vbeprintinfo(Vbe *vbe)
|
|
{
|
|
uint8_t *p;
|
|
VGAreg u;
|
|
|
|
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);
|
|
}
|
|
|
|
uint8_t*
|
|
vbemodes(Vbe *vbe)
|
|
{
|
|
uint8_t *p;
|
|
VGAreg 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)
|
|
{
|
|
int o;
|
|
uint32_t d, c, x;
|
|
uint8_t *p;
|
|
char tmp[sizeof m->chan];
|
|
VGAreg u;
|
|
|
|
p = vbesetup(vbe, &u, 0x4F01);
|
|
u.cx = id;
|
|
if(vbecall(vbe, &u) < 0)
|
|
return -1;
|
|
|
|
m->id = id;
|
|
m->attr = WORD(p);
|
|
m->bpl = WORD(p+16);
|
|
m->dx = WORD(p+18);
|
|
m->dy = WORD(p+20);
|
|
m->depth = p[25];
|
|
m->model = p[27] < nelem(modelstr) ? modelstr[p[27]] : "unknown";
|
|
m->r = p[31];
|
|
m->g = p[33];
|
|
m->b = p[35];
|
|
m->x = p[37];
|
|
m->ro = p[32];
|
|
m->go = p[34];
|
|
m->bo = p[36];
|
|
m->xo = p[38];
|
|
m->directcolor = p[39];
|
|
m->paddr = LONG(p+40);
|
|
snprint(m->name, sizeof m->name, "%dx%dx%d",
|
|
m->dx, m->dy, m->depth);
|
|
if(m->depth <= 8) {
|
|
snprint(m->chan, sizeof m->chan, "m%d", m->depth);
|
|
return 0;
|
|
}
|
|
|
|
m->xo = m->x = 0;
|
|
d = 1 << (m->depth - 1);
|
|
d |= d - 1;
|
|
c = ((1<<m->r)-1) << m->ro;
|
|
c |= ((1<<m->g)-1) << m->go;
|
|
c |= ((1<<m->b)-1) << m->bo;
|
|
x = d ^ c;
|
|
if(x != 0){
|
|
for(; (x & 1) == 0; x >>= 1)
|
|
m->xo++;
|
|
for(; x & 1; x >>= 1)
|
|
m->x++;
|
|
}
|
|
|
|
m->chan[0] = o = 0;
|
|
while(o < m->depth){
|
|
if(m->r && m->ro == o){
|
|
snprint(tmp, sizeof tmp, "r%d%s", m->r, m->chan);
|
|
o += m->r;
|
|
}else if(m->g && m->go == o){
|
|
snprint(tmp, sizeof tmp, "g%d%s", m->g, m->chan);
|
|
o += m->g;
|
|
}else if(m->b && m->bo == o){
|
|
snprint(tmp, sizeof tmp, "b%d%s", m->b, m->chan);
|
|
o += m->b;
|
|
}else if(m->x && m->xo == o){
|
|
snprint(tmp, sizeof tmp, "x%d%s", m->x, m->chan);
|
|
o += m->x;
|
|
}else
|
|
break;
|
|
strncpy(m->chan, tmp, sizeof m->chan);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
vbeprintmodeinfo(Vbe *vbe, int id, char *suffix)
|
|
{
|
|
Vmode m;
|
|
|
|
if(vbemodeinfo(vbe, id, &m) < 0){
|
|
// Bprint(&stdout, "vesa: cannot get mode 0x%ux: %r\n", id);
|
|
return;
|
|
}
|
|
printitem("vesa", "mode");
|
|
Bprint(&stdout, "0x%ux %s %s %s%s\n",
|
|
m.id, m.name, m.chan, m.model, suffix);
|
|
}
|
|
|
|
int
|
|
vbegetmode(Vbe *vbe)
|
|
{
|
|
VGAreg u;
|
|
|
|
vbesetup(vbe, &u, 0x4F03);
|
|
if(vbecall(vbe, &u) < 0)
|
|
return 0;
|
|
return u.bx;
|
|
}
|
|
|
|
int
|
|
vbesetmode(Vbe *vbe, int id)
|
|
{
|
|
uint8_t *p;
|
|
VGAreg u;
|
|
|
|
p = vbesetup(vbe, &u, 0x4F02);
|
|
if(id != 3)
|
|
id |= 3<<14; /* graphics: use linear, do not clear */
|
|
u.bx = id;
|
|
USED(p);
|
|
/*
|
|
* can set mode specifics (ht hss hse vt vss vse 0 clockhz refreshhz):
|
|
*
|
|
u.bx |= 1<<11;
|
|
n = atoi(argv[2]); PWORD(p, n); p+=2;
|
|
n = atoi(argv[3]); PWORD(p, n); p+=2;
|
|
n = atoi(argv[4]); PWORD(p, n); p+=2;
|
|
n = atoi(argv[5]); PWORD(p, n); p+=2;
|
|
n = atoi(argv[6]); PWORD(p, n); p+=2;
|
|
n = atoi(argv[7]); PWORD(p, n); p+=2;
|
|
*p++ = atoi(argv[8]);
|
|
n = atoi(argv[9]); PLONG(p, n); p += 4;
|
|
n = atoi(argv[10]); PWORD(p, n); p += 2;
|
|
if(p != vbe.buf+19){
|
|
fprint(2, "prog error\n");
|
|
return;
|
|
}
|
|
*
|
|
*/
|
|
return vbecall(vbe, &u);
|
|
}
|
|
|
|
void
|
|
vesatextmode(void)
|
|
{
|
|
if(vbe == nil){
|
|
vbe = mkvbe();
|
|
if(!vbe)
|
|
error("mkvbe: %r\n");
|
|
}
|
|
if(vbecheck(vbe) < 0)
|
|
error("vbecheck: %r\n");
|
|
if(vbesetmode(vbe, 3) < 0)
|
|
error("vbesetmode: %r\n");
|
|
}
|
|
|
|
static Flag edidflags[] = {
|
|
Fdigital, "digital",
|
|
Fdpmsstandby, "standby",
|
|
Fdpmssuspend, "suspend",
|
|
Fdpmsactiveoff, "activeoff",
|
|
Fmonochrome, "monochrome",
|
|
Fgtf, "gtf",
|
|
0
|
|
};
|
|
|
|
int parseedid128(Edid *e, void *v);
|
|
|
|
int
|
|
vbeddcedid(Vbe *vbe, Edid *e)
|
|
{
|
|
uint8_t *p;
|
|
VGAreg u;
|
|
|
|
p = vbesetup(vbe, &u, 0x4F15);
|
|
u.bx = 0x0001;
|
|
if(vbecall(vbe, &u) < 0)
|
|
return -1;
|
|
if(parseedid128(e, p) < 0){
|
|
werrstr("parseedid128: %r");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
printedid(Edid *e)
|
|
{
|
|
Modelist *l;
|
|
|
|
printitem("edid", "mfr");
|
|
Bprint(&stdout, "%s\n", e->mfr);
|
|
printitem("edid", "serialstr");
|
|
Bprint(&stdout, "%s\n", e->serialstr);
|
|
printitem("edid", "name");
|
|
Bprint(&stdout, "%s\n", e->name);
|
|
printitem("edid", "product");
|
|
Bprint(&stdout, "%d\n", e->product);
|
|
printitem("edid", "serial");
|
|
Bprint(&stdout, "%lud\n", e->serial);
|
|
printitem("edid", "version");
|
|
Bprint(&stdout, "%d.%d\n", e->version, e->revision);
|
|
printitem("edid", "mfrdate");
|
|
Bprint(&stdout, "%d.%d\n", e->mfryear, e->mfrweek);
|
|
printitem("edid", "size (cm)");
|
|
Bprint(&stdout, "%dx%d\n", e->dxcm, e->dycm);
|
|
printitem("edid", "gamma");
|
|
Bprint(&stdout, "%.2f\n", e->gamma/100.);
|
|
printitem("edid", "vert (Hz)");
|
|
Bprint(&stdout, "%d-%d\n", e->rrmin, e->rrmax);
|
|
printitem("edid", "horz (Hz)");
|
|
Bprint(&stdout, "%d-%d\n", e->hrmin, e->hrmax);
|
|
printitem("edid", "pclkmax");
|
|
Bprint(&stdout, "%lud\n", e->pclkmax);
|
|
printitem("edid", "flags");
|
|
printflags(edidflags, e->flags);
|
|
|
|
for(l=e->modelist; l; l=l->next){
|
|
printitem("edid", l->name);
|
|
Bprint(&stdout, "\n\t\tclock=%g\n\t\tshb=%d ehb=%d ht=%d\n\t\tvrs=%d vre=%d vt=%d\n\t\thsync=%c vsync=%c %s\n",
|
|
l->frequency/1.e6, l->shb, l->ehb, l->ht, l->vrs, l->vre, l->vt, l->hsync?l->hsync:'?', l->vsync?l->vsync:'?', l->interlace?"interlace=v" : "");
|
|
}
|
|
}
|
|
|
|
Modelist*
|
|
addmode(Modelist *l, Mode m)
|
|
{
|
|
int rr;
|
|
Modelist **lp;
|
|
|
|
//m.z = 8; // BUG
|
|
rr = (m.frequency+m.ht*m.vt/2)/(m.ht*m.vt);
|
|
snprint(m.name, sizeof m.name, "%dx%dx%d@%dHz", m.x, m.y, m.z, rr);
|
|
|
|
if(m.shs == 0)
|
|
m.shs = m.shb;
|
|
if(m.ehs == 0)
|
|
m.ehs = m.ehb;
|
|
if(m.vbs == 0)
|
|
m.vbs = m.vrs;
|
|
if(m.vbe == 0)
|
|
m.vbe = m.vbs+1;
|
|
|
|
for(lp=&l; *lp; lp=&(*lp)->next){
|
|
if(strcmp((*lp)->name, m.name) == 0){
|
|
(*lp)->Mode = m;
|
|
return l;
|
|
}
|
|
}
|
|
|
|
*lp = alloc(sizeof(**lp));
|
|
(*lp)->Mode = m;
|
|
return l;
|
|
}
|
|
|
|
/*
|
|
* Parse VESA EDID information. Based on the VESA
|
|
* Extended Display Identification Data standard, Version 3,
|
|
* November 13, 1997. See /public/doc/vesa/edidv3.pdf.
|
|
*
|
|
* This only handles 128-byte EDID blocks. Until I find
|
|
* a monitor that produces 256-byte blocks, I'm not going
|
|
* to try to decode them.
|
|
*/
|
|
|
|
/*
|
|
* Established timings block. There is a bitmap
|
|
* that says whether each mode is supported. Most
|
|
* of these have VESA definitions. Those that don't are marked
|
|
* as such, and we ignore them (the lookup fails).
|
|
*/
|
|
static char *estabtime[] = {
|
|
"720x400@70Hz", /* non-VESA: IBM, VGA */
|
|
"720x400@88Hz", /* non-VESA: IBM, XGA2 */
|
|
"640x480@60Hz",
|
|
"640x480@67Hz", /* non-VESA: Apple, Mac II */
|
|
"640x480@72Hz",
|
|
"640x480@75Hz",
|
|
"800x600@56Hz",
|
|
"800x600@60Hz",
|
|
|
|
"800x600@72Hz",
|
|
"800x600@75Hz",
|
|
"832x624@75Hz", /* non-VESA: Apple, Mac II */
|
|
"1024x768i@87Hz", /* non-VESA: IBM */
|
|
"1024x768@60Hz",
|
|
"1024x768@70Hz",
|
|
"1024x768@75Hz",
|
|
"1280x1024@75Hz",
|
|
|
|
"1152x870@75Hz", /* non-VESA: Apple, Mac II */
|
|
};
|
|
|
|
/*
|
|
* Decode the EDID detailed timing block. See pp. 20-21 of the standard.
|
|
*/
|
|
static int
|
|
decodedtb(Mode *m, uint8_t *p)
|
|
{
|
|
int ha, hb, hso, hspw, rr, va, vb, vso, vspw;
|
|
/* int dxmm, dymm, hbord, vbord; */
|
|
|
|
memset(m, 0, sizeof *m);
|
|
|
|
m->frequency = ((p[1]<<8) | p[0]) * 10000;
|
|
|
|
ha = ((p[4] & 0xF0)<<4) | p[2]; /* horizontal active */
|
|
hb = ((p[4] & 0x0F)<<8) | p[3]; /* horizontal blanking */
|
|
va = ((p[7] & 0xF0)<<4) | p[5]; /* vertical active */
|
|
vb = ((p[7] & 0x0F)<<8) | p[6]; /* vertical blanking */
|
|
hso = ((p[11] & 0xC0)<<2) | p[8]; /* horizontal sync offset */
|
|
hspw = ((p[11] & 0x30)<<4) | p[9]; /* horizontal sync pulse width */
|
|
vso = ((p[11] & 0x0C)<<2) | ((p[10] & 0xF0)>>4); /* vertical sync offset */
|
|
vspw = ((p[11] & 0x03)<<4) | (p[10] & 0x0F); /* vertical sync pulse width */
|
|
|
|
/* dxmm = (p[14] & 0xF0)<<4) | p[12]; *//* horizontal image size (mm) */
|
|
/* dymm = (p[14] & 0x0F)<<8) | p[13]; *//* vertical image size (mm) */
|
|
/* hbord = p[15]; *//* horizontal border (pixels) */
|
|
/* vbord = p[16]; *//* vertical border (pixels) */
|
|
|
|
m->x = ha;
|
|
m->y = va;
|
|
|
|
m->ht = ha+hb;
|
|
m->shs = ha;
|
|
m->shb = ha+hso;
|
|
m->ehb = ha+hso+hspw;
|
|
m->ehs = ha+hb;
|
|
|
|
m->vt = va+vb;
|
|
m->vbs = va;
|
|
m->vrs = va+vso;
|
|
m->vre = va+vso+vspw;
|
|
m->vbe = va+vb;
|
|
|
|
if(p[17] & 0x80) /* interlaced */
|
|
m->interlace = 'v';
|
|
|
|
if(p[17] & 0x60) /* some form of stereo monitor mode; no support */
|
|
return -1;
|
|
|
|
/*
|
|
* Sync signal description. I have no idea how to properly handle the
|
|
* first three cases, which I think are aimed at things other than
|
|
* canonical SVGA monitors.
|
|
*/
|
|
switch((p[17] & 0x18)>>3) {
|
|
case 0: /* analog composite sync signal*/
|
|
case 1: /* bipolar analog composite sync signal */
|
|
/* p[17] & 0x04 means serration: hsync during vsync */
|
|
/* p[17] & 0x02 means sync pulse appears on RGB not just G */
|
|
break;
|
|
|
|
case 2: /* digital composite sync signal */
|
|
/* p[17] & 0x04 means serration: hsync during vsync */
|
|
/* p[17] & 0x02 means hsync positive outside vsync */
|
|
break;
|
|
|
|
case 3: /* digital separate sync signal; the norm */
|
|
m->vsync = (p[17] & 0x04) ? '+' : '-';
|
|
m->hsync = (p[17] & 0x02) ? '+' : '-';
|
|
break;
|
|
}
|
|
/* p[17] & 0x01 is another stereo bit, only referenced if p[17] & 0x60 != 0 */
|
|
|
|
rr = (m->frequency+m->ht*m->vt/2) / (m->ht*m->vt);
|
|
|
|
snprint(m->name, sizeof m->name, "%dx%d@%dHz", m->x, m->y, rr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern Mode *vesamodes[];
|
|
|
|
int
|
|
vesalookup(Mode *m, char *name)
|
|
{
|
|
Mode **p;
|
|
|
|
for(p=vesamodes; *p; p++)
|
|
if(strcmp((*p)->name, name) == 0) {
|
|
*m = **p;
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
decodesti(Mode *m, uint8_t *p)
|
|
{
|
|
int x, y, rr;
|
|
char str[20];
|
|
|
|
x = (p[0]+31)*8;
|
|
switch((p[1]>>6) & 3){
|
|
default:
|
|
case 0:
|
|
y = x;
|
|
break;
|
|
case 1:
|
|
y = (x*4)/3;
|
|
break;
|
|
case 2:
|
|
y = (x*5)/4;
|
|
break;
|
|
case 3:
|
|
y = (x*16)/9;
|
|
break;
|
|
}
|
|
rr = (p[1] & 0x1F) + 60;
|
|
|
|
sprint(str, "%dx%d@%dHz", x, y, rr);
|
|
return vesalookup(m, str);
|
|
}
|
|
|
|
int
|
|
parseedid128(Edid *e, void *v)
|
|
{
|
|
static uint8_t magic[8] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };
|
|
uint8_t *p, *q, sum;
|
|
int dpms, estab, i, m, vid;
|
|
Mode mode;
|
|
|
|
memset(e, 0, sizeof *e);
|
|
|
|
p = (uint8_t*)v;
|
|
if(memcmp(p, magic, 8) != 0) {
|
|
werrstr("bad edid header");
|
|
return -1;
|
|
}
|
|
|
|
sum = 0;
|
|
for(i=0; i<128; i++)
|
|
sum += p[i];
|
|
if(sum != 0) {
|
|
werrstr("bad edid checksum");
|
|
return -1;
|
|
}
|
|
p += 8;
|
|
|
|
assert(p == (uint8_t*)v+8); /* assertion offsets from pp. 12-13 of the standard */
|
|
/*
|
|
* Manufacturer name is three 5-bit ascii letters, packed
|
|
* into a big endian [sic] short in big endian order. The high bit is unused.
|
|
*/
|
|
i = (p[0]<<8) | p[1];
|
|
p += 2;
|
|
e->mfr[0] = 'A'-1 + ((i>>10) & 0x1F);
|
|
e->mfr[1] = 'A'-1 + ((i>>5) & 0x1F);
|
|
e->mfr[2] = 'A'-1 + (i & 0x1F);
|
|
e->mfr[3] = '\0';
|
|
|
|
/*
|
|
* Product code is a little endian short.
|
|
*/
|
|
e->product = (p[1]<<8) | p[0];
|
|
p += 2;
|
|
|
|
/*
|
|
* Serial number is a little endian long, 0x01010101 = unused.
|
|
*/
|
|
e->serial = (p[3]<<24) | (p[2]<<16) | (p[1]<<8) | p[0];
|
|
p += 4;
|
|
if(e->serial == 0x01010101)
|
|
e->serial = 0;
|
|
|
|
e->mfrweek = *p++;
|
|
e->mfryear = 1990 + *p++;
|
|
|
|
assert(p == (uint8_t*)v+8+10);
|
|
/*
|
|
* Structure version is next two bytes: major.minor.
|
|
*/
|
|
e->version = *p++;
|
|
e->revision = *p++;
|
|
|
|
assert(p == (uint8_t*)v+8+10+2);
|
|
/*
|
|
* Basic display parameters / features.
|
|
*/
|
|
/*
|
|
* Video input definition byte: 0x80 tells whether it is
|
|
* an analog or digital screen; we ignore the other bits.
|
|
* See p. 15 of the standard.
|
|
*/
|
|
vid = *p++;
|
|
if(vid & 0x80)
|
|
e->flags |= Fdigital;
|
|
|
|
e->dxcm = *p++;
|
|
e->dycm = *p++;
|
|
e->gamma = 100 + *p++;
|
|
dpms = *p++;
|
|
if(dpms & 0x80)
|
|
e->flags |= Fdpmsstandby;
|
|
if(dpms & 0x40)
|
|
e->flags |= Fdpmssuspend;
|
|
if(dpms & 0x20)
|
|
e->flags |= Fdpmsactiveoff;
|
|
if((dpms & 0x18) == 0x00)
|
|
e->flags |= Fmonochrome;
|
|
if(dpms & 0x01)
|
|
e->flags |= Fgtf;
|
|
|
|
assert(p == (uint8_t*)v+8+10+2+5);
|
|
/*
|
|
* Color characteristics currently ignored.
|
|
*/
|
|
p += 10;
|
|
|
|
assert(p == (uint8_t*)v+8+10+2+5+10);
|
|
/*
|
|
* Established timings: a bitmask of 19 preset timings.
|
|
*/
|
|
estab = (p[0]<<16) | (p[1]<<8) | p[2];
|
|
p += 3;
|
|
|
|
for(i=0, m=1<<23; i<nelem(estabtime); i++, m>>=1)
|
|
if(estab & m)
|
|
if(vesalookup(&mode, estabtime[i]) == 0)
|
|
e->modelist = addmode(e->modelist, mode);
|
|
|
|
assert(p == (uint8_t*)v+8+10+2+5+10+3);
|
|
/*
|
|
* Standard Timing Identifications: eight 2-byte selectors
|
|
* of more standard timings.
|
|
*/
|
|
for(i=0; i<8; i++, p+=2)
|
|
if(decodesti(&mode, p+2*i) == 0)
|
|
e->modelist = addmode(e->modelist, mode);
|
|
|
|
assert(p == (uint8_t*)v+8+10+2+5+10+3+16);
|
|
/*
|
|
* Detailed Timings
|
|
*/
|
|
fprint(2, "dt\n");
|
|
for(i=0; i<4; i++, p+=18) {
|
|
fprint(2, "%.8H\n", p);
|
|
if(p[0] || p[1]) { /* detailed timing block: p[0] or p[1] != 0 */
|
|
if(decodedtb(&mode, p) == 0)
|
|
e->modelist = addmode(e->modelist, mode);
|
|
} else if(p[2]==0) { /* monitor descriptor block */
|
|
switch(p[3]) {
|
|
case 0xFF: /* monitor serial number (13-byte ascii, 0A terminated) */
|
|
if(q = memchr(p+5, 0x0A, 13))
|
|
*q = '\0';
|
|
memset(e->serialstr, 0, sizeof(e->serialstr));
|
|
strncpy(e->serialstr, (char*)p+5, 13);
|
|
break;
|
|
case 0xFE: /* ascii string (13-byte ascii, 0A terminated) */
|
|
break;
|
|
case 0xFD: /* monitor range limits */
|
|
print("fd %.18H\n", p);
|
|
e->rrmin = p[5];
|
|
e->rrmax = p[6];
|
|
e->hrmin = p[7]*1000;
|
|
e->hrmax = p[8]*1000;
|
|
if(p[9] != 0xFF)
|
|
e->pclkmax = p[9]*10*1000000;
|
|
break;
|
|
case 0xFC: /* monitor name (13-byte ascii, 0A terminated) */
|
|
if(q = memchr(p+5, 0x0A, 13))
|
|
*q = '\0';
|
|
memset(e->name, 0, sizeof(e->name));
|
|
strncpy(e->name, (char*)p+5, 13);
|
|
break;
|
|
case 0xFB: /* extra color point data */
|
|
break;
|
|
case 0xFA: /* extra standard timing identifications */
|
|
for(i=0; i<6; i++)
|
|
if(decodesti(&mode, p+5+2*i) == 0)
|
|
e->modelist = addmode(e->modelist, mode);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(p == (uint8_t*)v+8+10+2+5+10+3+16+72);
|
|
return 0;
|
|
}
|