summaryrefslogtreecommitdiffstats
path: root/tools/ddcprobe/vbe.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/ddcprobe/vbe.c')
-rw-r--r--tools/ddcprobe/vbe.c411
1 files changed, 411 insertions, 0 deletions
diff --git a/tools/ddcprobe/vbe.c b/tools/ddcprobe/vbe.c
new file mode 100644
index 000000000..b710c79bb
--- /dev/null
+++ b/tools/ddcprobe/vbe.c
@@ -0,0 +1,411 @@
+#include <sys/types.h>
+#if defined(__i386__) || defined(__x86_64__) || defined(__ia64__)
+#include <sys/io.h>
+#endif
+#include <sys/mman.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "vesamode.h"
+#include "vbe.h"
+#ifdef HAVE_VBE
+#include "int10/vbios.h"
+#endif
+
+#define DEBUG 0
+#if DEBUG
+#define bug printf
+#define D(X) X
+#else
+#define D(X)
+#endif
+
+#ifdef __i386__
+#define cpuemu 1
+#else
+#define cpuemu 0
+#endif
+
+#ifdef HAVE_VBE
+/*
+ * Create a 'canonical' version, i.e. no spaces at start and end.
+ *
+ * Note: removes chars >= 0x80 as well (due to (char *))! This
+ * is currently considered a feature.
+ */
+static char *canon_str(char *s, int len)
+{
+ char *m2, *m1, *m0 = malloc(len + 1);
+ int i;
+
+ for(m1 = m0, i = 0; i < len; i++) {
+ if(m1 == m0 && s[i] <= ' ') continue;
+ *m1++ = s[i];
+ }
+ *m1 = 0;
+ while(m1 > m0 && m1[-1] <= ' ') {
+ *--m1 = 0;
+ }
+
+ m2 = strdup(m0);
+ free(m0);
+
+ return m2;
+}
+
+static unsigned segofs2addr(unsigned char *segofs)
+{
+ return segofs[0] + (segofs[1] << 8) + (segofs[2] << 4)+ (segofs[3] << 12);
+}
+
+
+static unsigned get_data(unsigned char *buf, unsigned buf_size, unsigned addr)
+{
+ unsigned bufferaddr = 0x7e00;
+ unsigned len;
+
+ *buf = 0;
+ len = 0;
+
+ if(addr >= bufferaddr && addr < bufferaddr + 0x200) {
+ len = bufferaddr + 0x200 - addr;
+ if(len >= buf_size) len = buf_size - 1;
+ memcpy(buf, addr + (char *) 0, len);
+ }
+ else if(addr >= 0x0c0000 && addr < 0x100000) {
+ len = 0x100000 - addr;
+ if(len >= buf_size) len = buf_size - 1;
+ memcpy(buf, addr + (char *) 0, len);
+ }
+
+ buf[len] = 0;
+
+ return len;
+}
+
+#define GET_WORD(ADDR, OFS) ((ADDR)[OFS] + ((ADDR)[(OFS) + 1] << 8))
+
+int vbe_get_vbe_info(struct vbe_info *vbe)
+{
+ int i, l, u;
+ unsigned char v[0x200];
+ unsigned char tmp[1024];
+ int ax, bx, cx;
+
+ if (vbe == NULL)
+ return 0;
+
+ /* Setup registers for the interrupt call */
+ ax = 0x4f00;
+ bx = 0;
+ cx = 0;
+ memset(v, 0, sizeof(v));
+ strcpy(v, "VBE2");
+
+ /* Get VBE block */
+ i = CallInt10(&ax, &bx, &cx, v, sizeof(v), cpuemu) & 0xffff;
+ if (i != 0x4f) {
+ D(bug("VBE: Error (0x4f00): 0x%04x\n", i));
+ return 0;
+ }
+
+ /* Parse VBE block */
+ vbe->version = GET_WORD(v, 0x04);
+ vbe->oem_version = GET_WORD(v, 0x14);
+ vbe->memory_size = GET_WORD(v, 0x12) << 16;
+ D(bug("version = %u.%u, oem version = %u.%u\n",
+ vbe->version >> 8, vbe->version & 0xff, vbe->oem_version >> 8, vbe->oem_version & 0xff));
+ D(bug("memory = %uk\n", vbe->memory_size >> 10));
+
+ l = get_data(tmp, sizeof tmp, u = segofs2addr(v + 0x06));
+ vbe->oem_name = canon_str(tmp, l);
+ D(bug("oem name [0x%05x] = \"%s\"\n", u, vbe->oem_name));
+
+ l = get_data(tmp, sizeof tmp, u = segofs2addr(v + 0x16));
+ vbe->vendor_name = canon_str(tmp, l);
+ D(bug("vendor name [0x%05x] = \"%s\"\n", u, vbe->vendor_name));
+
+ l = get_data(tmp, sizeof tmp, u = segofs2addr(v + 0x1a));
+ vbe->product_name = canon_str(tmp, l);
+ D(bug("product name [0x%05x] = \"%s\"\n", u, vbe->product_name));
+
+ l = get_data(tmp, sizeof tmp, u = segofs2addr(v + 0x1e));
+ vbe->product_revision = canon_str(tmp, l);
+ D(bug("product revision [0x%05x] = \"%s\"\n", u, vbe->product_revision));
+
+ l = get_data(tmp, sizeof tmp, u = segofs2addr(v + 0x0e)) >> 1;
+ for(i = vbe->modes = 0; i < l && i < sizeof vbe->mode_list / sizeof *vbe->mode_list; i++) {
+ u = GET_WORD(tmp, 2 * i);
+ if(u != 0xffff)
+ vbe->mode_list[vbe->modes++] = u;
+ else
+ break;
+ }
+ D(bug("%u video modes\n", vbe->modes));
+
+ return 1;
+}
+
+/* Get EDID info. */
+int vbe_get_edid_info(struct vbe_edid1_info *edid)
+{
+ int i;
+ int ax, bx, cx;
+
+ if (edid == NULL)
+ return 0;
+
+ /* Setup registers for the interrupt call */
+ ax = 0x4f15;
+ bx = 1;
+ cx = 0;
+
+ /* Get EDID block */
+ i = CallInt10(&ax, &bx, &cx, (unsigned char *)edid, sizeof *edid, cpuemu) & 0xffff;
+ if (i != 0x4f) {
+ D(bug("EDID: Error (0x4f15): 0x%04x\n", i));
+ return 0;
+ }
+
+ edid->manufacturer_name.p = ntohs(edid->manufacturer_name.p);
+ return 1;
+}
+#endif
+
+/* Just read ranges from the EDID. */
+void vbe_get_edid_ranges(struct vbe_edid1_info *edid,
+ unsigned char *hmin, unsigned char *hmax,
+ unsigned char *vmin, unsigned char *vmax)
+{
+ struct vbe_edid_monitor_descriptor *monitor;
+ int i;
+
+ *hmin = *hmax = *vmin = *vmax = 0;
+
+ for(i = 0; i < 4; i++) {
+ monitor = &edid->monitor_details.monitor_descriptor[i];
+ if(monitor->type == vbe_edid_monitor_descriptor_range) {
+ *hmin = monitor->data.range_data.horizontal_min;
+ *hmax = monitor->data.range_data.horizontal_max;
+ *vmin = monitor->data.range_data.vertical_min;
+ *vmax = monitor->data.range_data.vertical_max;
+ }
+ }
+}
+
+static int compare_vbe_modelines(const void *m1, const void *m2)
+{
+ const struct vbe_modeline *M1 = (const struct vbe_modeline*) m1;
+ const struct vbe_modeline *M2 = (const struct vbe_modeline*) m2;
+ if(M1->width < M2->width) return -1;
+ if(M1->width > M2->width) return 1;
+ return 0;
+}
+
+struct vbe_modeline *vbe_get_edid_modelines(struct vbe_edid1_info *edid)
+{
+ struct vbe_modeline *ret;
+ char buf[LINE_MAX];
+ int modeline_count = 0, i, j;
+
+ if (edid == NULL)
+ return NULL;
+
+ memcpy(buf, &edid->established_timings,
+ sizeof(edid->established_timings));
+ for(i = 0; i < (8 * sizeof(edid->established_timings)); i++) {
+ if(buf[i / 8] & (1 << (i % 8))) {
+ modeline_count++;
+ }
+ }
+
+ /* Count the number of standard timings. */
+ for(i = 0; i < 8; i++) {
+ int x, v;
+ x = edid->standard_timing[i].xresolution;
+ v = edid->standard_timing[i].vfreq;
+ if(((edid->standard_timing[i].xresolution & 0x01) != x) &&
+ ((edid->standard_timing[i].vfreq & 0x01) != v)) {
+ modeline_count++;
+ }
+ }
+
+ ret = malloc(sizeof(struct vbe_modeline) * (modeline_count + 1));
+ if(ret == NULL) {
+ return NULL;
+ }
+ memset(ret, 0, sizeof(struct vbe_modeline) * (modeline_count + 1));
+
+ modeline_count = 0;
+
+ /* Fill out established timings. */
+ if(edid->established_timings.timing_720x400_70) {
+ ret[modeline_count].width = 720;
+ ret[modeline_count].height = 400;
+ ret[modeline_count].refresh = 70;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_720x400_88) {
+ ret[modeline_count].width = 720;
+ ret[modeline_count].height = 400;
+ ret[modeline_count].refresh = 88;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_640x480_60) {
+ ret[modeline_count].width = 640;
+ ret[modeline_count].height = 480;
+ ret[modeline_count].refresh = 60;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_640x480_67) {
+ ret[modeline_count].width = 640;
+ ret[modeline_count].height = 480;
+ ret[modeline_count].refresh = 67;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_640x480_72) {
+ ret[modeline_count].width = 640;
+ ret[modeline_count].height = 480;
+ ret[modeline_count].refresh = 72;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_640x480_75) {
+ ret[modeline_count].width = 640;
+ ret[modeline_count].height = 480;
+ ret[modeline_count].refresh = 75;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_800x600_56) {
+ ret[modeline_count].width = 800;
+ ret[modeline_count].height = 600;
+ ret[modeline_count].refresh = 56;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_800x600_60) {
+ ret[modeline_count].width = 800;
+ ret[modeline_count].height = 600;
+ ret[modeline_count].refresh = 60;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_800x600_72) {
+ ret[modeline_count].width = 800;
+ ret[modeline_count].height = 600;
+ ret[modeline_count].refresh = 72;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_800x600_75) {
+ ret[modeline_count].width = 800;
+ ret[modeline_count].height = 600;
+ ret[modeline_count].refresh = 75;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_832x624_75) {
+ ret[modeline_count].width = 832;
+ ret[modeline_count].height = 624;
+ ret[modeline_count].refresh = 75;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_1024x768_87i) {
+ ret[modeline_count].width = 1024;
+ ret[modeline_count].height = 768;
+ ret[modeline_count].refresh = 87;
+ ret[modeline_count].interlaced = 1;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_1024x768_60){
+ ret[modeline_count].width = 1024;
+ ret[modeline_count].height = 768;
+ ret[modeline_count].refresh = 60;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_1024x768_70){
+ ret[modeline_count].width = 1024;
+ ret[modeline_count].height = 768;
+ ret[modeline_count].refresh = 70;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_1024x768_75){
+ ret[modeline_count].width = 1024;
+ ret[modeline_count].height = 768;
+ ret[modeline_count].refresh = 75;
+ modeline_count++;
+ }
+ if(edid->established_timings.timing_1280x1024_75) {
+ ret[modeline_count].width = 1280;
+ ret[modeline_count].height = 1024;
+ ret[modeline_count].refresh = 75;
+ modeline_count++;
+ }
+
+ /* Add in standard timings. */
+ for(i = 0; i < 8; i++) {
+ float aspect = 1;
+ int x, v;
+ x = edid->standard_timing[i].xresolution;
+ v = edid->standard_timing[i].vfreq;
+ if(((edid->standard_timing[i].xresolution & 0x01) != x) &&
+ ((edid->standard_timing[i].vfreq & 0x01) != v)) {
+ switch(edid->standard_timing[i].aspect) {
+ case aspect_75: aspect = 0.7500; break;
+ case aspect_8: aspect = 0.8000; break;
+ case aspect_5625: aspect = 0.5625; break;
+ default: aspect = 1; break;
+ }
+ x = (edid->standard_timing[i].xresolution + 31) * 8;
+ ret[modeline_count].width = x;
+ ret[modeline_count].height = x * aspect;
+ ret[modeline_count].refresh =
+ edid->standard_timing[i].vfreq + 60;
+ modeline_count++;
+ }
+ }
+
+ /* Now tack on any matching modelines. */
+ for(i = 0; ret[i].refresh != 0; i++) {
+ struct vesa_timing_t *t = NULL;
+ for(j = 0; known_vesa_timings[j].refresh != 0; j++) {
+ t = &known_vesa_timings[j];
+ if(ret[i].width == t->x)
+ if(ret[i].height == t->y)
+ if(ret[i].refresh == t->refresh) {
+ snprintf(buf, sizeof(buf),
+ "ModeLine \"%dx%d\"\t%6.2f "
+ "%4d %4d %4d %4d %4d %4d %4d %4d %s %s"
+ , t->x, t->y, t->dotclock,
+ t->timings[0],
+ t->timings[0] + t->timings[1],
+ t->timings[0] + t->timings[1] +
+ t->timings[2],
+ t->timings[0] + t->timings[1] +
+ t->timings[2] + t->timings[3],
+ t->timings[4],
+ t->timings[4] + t->timings[5],
+ t->timings[4] + t->timings[5] +
+ t->timings[6],
+ t->timings[4] + t->timings[5] +
+ t->timings[6] + t->timings[7],
+ t->hsync == hsync_pos ?
+ "+hsync" : "-hsync",
+ t->vsync == vsync_pos ?
+ "+vsync" : "-vsync");
+ ret[i].modeline = strdup(buf);
+ ret[i].hfreq = t->hfreq;
+ ret[i].vfreq = t->vfreq;
+ }
+ }
+ }
+
+ modeline_count = 0;
+ for(i = 0; ret[i].refresh != 0; i++) {
+ modeline_count++;
+ }
+ qsort(ret, modeline_count, sizeof(ret[0]), compare_vbe_modelines);
+
+ return ret;
+}