summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Roberts <timr@probo.com>2002-09-12 00:26:00 +0200
committerTormod Volden <debian.tormod@gmail.com>2011-11-06 20:46:20 +0100
commit31999fea5bca32baa7cbe38a54bd88d0888939ab (patch)
treef4d7e8d069a1bec81cc8fba7abb4d72ae03c5885
Imported Tim Roberts' original codes3ssrc.zip
From http://www.probo.com/timr/s3/s3ssrc.zip md5sum 1328b070343ac79c5ed4c613a1113754 s3ssrc.zip
-rw-r--r--Makefile5
-rw-r--r--lrmi.c883
-rw-r--r--lrmi.h85
-rw-r--r--s3switch.1x62
-rw-r--r--s3switch.c454
5 files changed, 1489 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ce1050f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+CC = gcc -g
+
+all: s3switch
+
+s3switch: s3switch.o lrmi.o
diff --git a/lrmi.c b/lrmi.c
new file mode 100644
index 0000000..f5abf93
--- /dev/null
+++ b/lrmi.c
@@ -0,0 +1,883 @@
+/*
+Linux Real Mode Interface - A library of DPMI-like functions for Linux.
+
+Copyright (C) 1998 by Josh Vanderhoof
+
+You are free to distribute and modify this file, as long as you
+do not remove this copyright notice and clearly label modified
+versions as being modified.
+
+This software has NO WARRANTY. Use it at your own risk.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <asm/vm86.h>
+
+#ifdef USE_LIBC_VM86
+#include <sys/vm86.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "lrmi.h"
+
+#define REAL_MEM_BASE ((void *)0x10000)
+#define REAL_MEM_SIZE 0x10000
+#define REAL_MEM_BLOCKS 0x100
+
+struct mem_block
+ {
+ unsigned int size : 20;
+ unsigned int free : 1;
+ };
+
+static struct
+ {
+ int ready;
+ int count;
+ struct mem_block blocks[REAL_MEM_BLOCKS];
+ } mem_info = { 0 };
+
+static int
+real_mem_init(void)
+ {
+ void *m;
+ int fd_zero;
+
+ if (mem_info.ready)
+ return 1;
+
+ fd_zero = open("/dev/zero", O_RDONLY);
+ if (fd_zero == -1)
+ {
+ perror("open /dev/zero");
+ return 0;
+ }
+
+ m = mmap((void *)REAL_MEM_BASE, REAL_MEM_SIZE,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_FIXED | MAP_PRIVATE, fd_zero, 0);
+
+ if (m == (void *)-1)
+ {
+ perror("mmap /dev/zero");
+ close(fd_zero);
+ return 0;
+ }
+
+ mem_info.ready = 1;
+ mem_info.count = 1;
+ mem_info.blocks[0].size = REAL_MEM_SIZE;
+ mem_info.blocks[0].free = 1;
+
+ return 1;
+ }
+
+
+static void
+insert_block(int i)
+ {
+ memmove(
+ mem_info.blocks + i + 1,
+ mem_info.blocks + i,
+ (mem_info.count - i) * sizeof(struct mem_block));
+
+ mem_info.count++;
+ }
+
+static void
+delete_block(int i)
+ {
+ mem_info.count--;
+
+ memmove(
+ mem_info.blocks + i,
+ mem_info.blocks + i + 1,
+ (mem_info.count - i) * sizeof(struct mem_block));
+ }
+
+void *
+LRMI_alloc_real(int size)
+ {
+ int i;
+ char *r = (char *)REAL_MEM_BASE;
+
+ if (!mem_info.ready)
+ return NULL;
+
+ if (mem_info.count == REAL_MEM_BLOCKS)
+ return NULL;
+
+ size = (size + 15) & ~15;
+
+ for (i = 0; i < mem_info.count; i++)
+ {
+ if (mem_info.blocks[i].free && size < mem_info.blocks[i].size)
+ {
+ insert_block(i);
+
+ mem_info.blocks[i].size = size;
+ mem_info.blocks[i].free = 0;
+ mem_info.blocks[i + 1].size -= size;
+
+ return (void *)r;
+ }
+
+ r += mem_info.blocks[i].size;
+ }
+
+ return NULL;
+ }
+
+
+void
+LRMI_free_real(void *m)
+ {
+ int i;
+ char *r = (char *)REAL_MEM_BASE;
+
+ if (!mem_info.ready)
+ return;
+
+ i = 0;
+ while (m != (void *)r)
+ {
+ r += mem_info.blocks[i].size;
+ i++;
+ if (i == mem_info.count)
+ return;
+ }
+
+ mem_info.blocks[i].free = 1;
+
+ if (i + 1 < mem_info.count && mem_info.blocks[i + 1].free)
+ {
+ mem_info.blocks[i].size += mem_info.blocks[i + 1].size;
+ delete_block(i + 1);
+ }
+
+ if (i - 1 >= 0 && mem_info.blocks[i - 1].free)
+ {
+ mem_info.blocks[i - 1].size += mem_info.blocks[i].size;
+ delete_block(i);
+ }
+ }
+
+
+#define DEFAULT_VM86_FLAGS (IF_MASK | IOPL_MASK)
+#define DEFAULT_STACK_SIZE 0x1000
+#define RETURN_TO_32_INT 255
+
+static struct
+ {
+ int ready;
+ unsigned short ret_seg, ret_off;
+ unsigned short stack_seg, stack_off;
+ struct vm86_struct vm;
+ } context = { 0 };
+
+
+static inline void
+set_bit(unsigned int bit, void *array)
+ {
+ unsigned char *a = array;
+
+ a[bit / 8] |= (1 << (bit % 8));
+ }
+
+
+static inline unsigned int
+get_int_seg(int i)
+ {
+ return *(unsigned short *)(i * 4 + 2);
+ }
+
+
+static inline unsigned int
+get_int_off(int i)
+ {
+ return *(unsigned short *)(i * 4);
+ }
+
+
+static inline void
+pushw(unsigned short i)
+ {
+ struct vm86_regs *r = &context.vm.regs;
+ r->esp -= 2;
+ *(unsigned short *)(((unsigned int)r->ss << 4) + r->esp) = i;
+ }
+
+
+int
+LRMI_init(void)
+ {
+ void *m;
+ int fd_mem;
+
+ if (context.ready)
+ return 1;
+
+ if (!real_mem_init())
+ return 0;
+
+ /*
+ Map the Interrupt Vectors (0x0 - 0x400) + BIOS data (0x400 - 0x502)
+ and the ROM (0xa0000 - 0x100000)
+ */
+ fd_mem = open("/dev/mem", O_RDWR);
+
+ if (fd_mem == -1)
+ {
+ perror("open /dev/mem");
+ return 0;
+ }
+
+ m = mmap((void *)0, 0x502,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_FIXED | MAP_PRIVATE, fd_mem, 0);
+
+ if (m == (void *)-1)
+ {
+ perror("mmap /dev/mem");
+ return 0;
+ }
+
+ m = mmap((void *)0xa0000, 0x100000 - 0xa0000,
+ PROT_READ | PROT_WRITE,
+ MAP_FIXED | MAP_SHARED, fd_mem, 0xa0000);
+
+ if (m == (void *)-1)
+ {
+ perror("mmap /dev/mem");
+ return 0;
+ }
+
+
+ /*
+ Allocate a stack
+ */
+ m = LRMI_alloc_real(DEFAULT_STACK_SIZE);
+
+ context.stack_seg = (unsigned int)m >> 4;
+ context.stack_off = DEFAULT_STACK_SIZE;
+
+ /*
+ Allocate the return to 32 bit routine
+ */
+ m = LRMI_alloc_real(2);
+
+ context.ret_seg = (unsigned int)m >> 4;
+ context.ret_off = (unsigned int)m & 0xf;
+
+ ((unsigned char *)m)[0] = 0xcd; /* int opcode */
+ ((unsigned char *)m)[1] = RETURN_TO_32_INT;
+
+ memset(&context.vm, 0, sizeof(context.vm));
+
+ /*
+ Enable kernel emulation of all ints except RETURN_TO_32_INT
+ */
+ memset(&context.vm.int_revectored, 0, sizeof(context.vm.int_revectored));
+ set_bit(RETURN_TO_32_INT, &context.vm.int_revectored);
+
+ context.ready = 1;
+
+ return 1;
+ }
+
+
+static void
+set_regs(struct LRMI_regs *r)
+ {
+ context.vm.regs.edi = r->edi;
+ context.vm.regs.esi = r->esi;
+ context.vm.regs.ebp = r->ebp;
+ context.vm.regs.ebx = r->ebx;
+ context.vm.regs.edx = r->edx;
+ context.vm.regs.ecx = r->ecx;
+ context.vm.regs.eax = r->eax;
+ context.vm.regs.eflags = DEFAULT_VM86_FLAGS;
+ context.vm.regs.es = r->es;
+ context.vm.regs.ds = r->ds;
+ context.vm.regs.fs = r->fs;
+ context.vm.regs.gs = r->gs;
+ }
+
+
+static void
+get_regs(struct LRMI_regs *r)
+ {
+ r->edi = context.vm.regs.edi;
+ r->esi = context.vm.regs.esi;
+ r->ebp = context.vm.regs.ebp;
+ r->ebx = context.vm.regs.ebx;
+ r->edx = context.vm.regs.edx;
+ r->ecx = context.vm.regs.ecx;
+ r->eax = context.vm.regs.eax;
+ r->flags = context.vm.regs.eflags;
+ r->es = context.vm.regs.es;
+ r->ds = context.vm.regs.ds;
+ r->fs = context.vm.regs.fs;
+ r->gs = context.vm.regs.gs;
+ }
+
+#define DIRECTION_FLAG (1 << 10)
+
+static void
+em_ins(int size)
+ {
+ unsigned int edx, edi;
+
+ edx = context.vm.regs.edx & 0xffff;
+ edi = context.vm.regs.edi & 0xffff;
+ edi += (unsigned int)context.vm.regs.ds << 4;
+
+ if (context.vm.regs.eflags & DIRECTION_FLAG)
+ {
+ if (size == 4)
+ asm volatile ("std; insl; cld"
+ : "=D" (edi) : "d" (edx), "0" (edi));
+ else if (size == 2)
+ asm volatile ("std; insw; cld"
+ : "=D" (edi) : "d" (edx), "0" (edi));
+ else
+ asm volatile ("std; insb; cld"
+ : "=D" (edi) : "d" (edx), "0" (edi));
+ }
+ else
+ {
+ if (size == 4)
+ asm volatile ("cld; insl"
+ : "=D" (edi) : "d" (edx), "0" (edi));
+ else if (size == 2)
+ asm volatile ("cld; insw"
+ : "=D" (edi) : "d" (edx), "0" (edi));
+ else
+ asm volatile ("cld; insb"
+ : "=D" (edi) : "d" (edx), "0" (edi));
+ }
+
+ edi -= (unsigned int)context.vm.regs.ds << 4;
+
+ context.vm.regs.edi &= 0xffff0000;
+ context.vm.regs.edi |= edi & 0xffff;
+ }
+
+static void
+em_rep_ins(int size)
+ {
+ unsigned int ecx, edx, edi;
+
+ ecx = context.vm.regs.ecx & 0xffff;
+ edx = context.vm.regs.edx & 0xffff;
+ edi = context.vm.regs.edi & 0xffff;
+ edi += (unsigned int)context.vm.regs.ds << 4;
+
+ if (context.vm.regs.eflags & DIRECTION_FLAG)
+ {
+ if (size == 4)
+ asm volatile ("std; rep; insl; cld"
+ : "=D" (edi), "=c" (ecx)
+ : "d" (edx), "0" (edi), "1" (ecx));
+ else if (size == 2)
+ asm volatile ("std; rep; insw; cld"
+ : "=D" (edi), "=c" (ecx)
+ : "d" (edx), "0" (edi), "1" (ecx));
+ else
+ asm volatile ("std; rep; insb; cld"
+ : "=D" (edi), "=c" (ecx)
+ : "d" (edx), "0" (edi), "1" (ecx));
+ }
+ else
+ {
+ if (size == 4)
+ asm volatile ("cld; rep; insl"
+ : "=D" (edi), "=c" (ecx)
+ : "d" (edx), "0" (edi), "1" (ecx));
+ else if (size == 2)
+ asm volatile ("cld; rep; insw"
+ : "=D" (edi), "=c" (ecx)
+ : "d" (edx), "0" (edi), "1" (ecx));
+ else
+ asm volatile ("cld; rep; insb"
+ : "=D" (edi), "=c" (ecx)
+ : "d" (edx), "0" (edi), "1" (ecx));
+ }
+
+ edi -= (unsigned int)context.vm.regs.ds << 4;
+
+ context.vm.regs.edi &= 0xffff0000;
+ context.vm.regs.edi |= edi & 0xffff;
+
+ context.vm.regs.ecx &= 0xffff0000;
+ context.vm.regs.ecx |= ecx & 0xffff;
+ }
+
+static void
+em_outs(int size)
+ {
+ unsigned int edx, esi;
+
+ edx = context.vm.regs.edx & 0xffff;
+ esi = context.vm.regs.esi & 0xffff;
+ esi += (unsigned int)context.vm.regs.ds << 4;
+
+ if (context.vm.regs.eflags & DIRECTION_FLAG)
+ {
+ if (size == 4)
+ asm volatile ("std; outsl; cld"
+ : "=S" (esi) : "d" (edx), "0" (esi));
+ else if (size == 2)
+ asm volatile ("std; outsw; cld"
+ : "=S" (esi) : "d" (edx), "0" (esi));
+ else
+ asm volatile ("std; outsb; cld"
+ : "=S" (esi) : "d" (edx), "0" (esi));
+ }
+ else
+ {
+ if (size == 4)
+ asm volatile ("cld; outsl"
+ : "=S" (esi) : "d" (edx), "0" (esi));
+ else if (size == 2)
+ asm volatile ("cld; outsw"
+ : "=S" (esi) : "d" (edx), "0" (esi));
+ else
+ asm volatile ("cld; outsb"
+ : "=S" (esi) : "d" (edx), "0" (esi));
+ }
+
+ esi -= (unsigned int)context.vm.regs.ds << 4;
+
+ context.vm.regs.esi &= 0xffff0000;
+ context.vm.regs.esi |= esi & 0xffff;
+ }
+
+static void
+em_rep_outs(int size)
+ {
+ unsigned int ecx, edx, esi;
+
+ ecx = context.vm.regs.ecx & 0xffff;
+ edx = context.vm.regs.edx & 0xffff;
+ esi = context.vm.regs.esi & 0xffff;
+ esi += (unsigned int)context.vm.regs.ds << 4;
+
+ if (context.vm.regs.eflags & DIRECTION_FLAG)
+ {
+ if (size == 4)
+ asm volatile ("std; rep; outsl; cld"
+ : "=S" (esi), "=c" (ecx)
+ : "d" (edx), "0" (esi), "1" (ecx));
+ else if (size == 2)
+ asm volatile ("std; rep; outsw; cld"
+ : "=S" (esi), "=c" (ecx)
+ : "d" (edx), "0" (esi), "1" (ecx));
+ else
+ asm volatile ("std; rep; outsb; cld"
+ : "=S" (esi), "=c" (ecx)
+ : "d" (edx), "0" (esi), "1" (ecx));
+ }
+ else
+ {
+ if (size == 4)
+ asm volatile ("cld; rep; outsl"
+ : "=S" (esi), "=c" (ecx)
+ : "d" (edx), "0" (esi), "1" (ecx));
+ else if (size == 2)
+ asm volatile ("cld; rep; outsw"
+ : "=S" (esi), "=c" (ecx)
+ : "d" (edx), "0" (esi), "1" (ecx));
+ else
+ asm volatile ("cld; rep; outsb"
+ : "=S" (esi), "=c" (ecx)
+ : "d" (edx), "0" (esi), "1" (ecx));
+ }
+
+ esi -= (unsigned int)context.vm.regs.ds << 4;
+
+ context.vm.regs.esi &= 0xffff0000;
+ context.vm.regs.esi |= esi & 0xffff;
+
+ context.vm.regs.ecx &= 0xffff0000;
+ context.vm.regs.ecx |= ecx & 0xffff;
+ }
+
+static void
+em_inb(void)
+ {
+ asm volatile ("inb (%w1), %b0"
+ : "=a" (context.vm.regs.eax)
+ : "d" (context.vm.regs.edx), "0" (context.vm.regs.eax));
+ }
+
+static void
+em_inw(void)
+ {
+ asm volatile ("inw (%w1), %w0"
+ : "=a" (context.vm.regs.eax)
+ : "d" (context.vm.regs.edx), "0" (context.vm.regs.eax));
+ }
+
+static void
+em_inl(void)
+ {
+ asm volatile ("inl (%w1), %0"
+ : "=a" (context.vm.regs.eax)
+ : "d" (context.vm.regs.edx));
+ }
+
+static void
+em_outb(void)
+ {
+ asm volatile ("outb %b0, (%w1)"
+ : : "a" (context.vm.regs.eax),
+ "d" (context.vm.regs.edx));
+ }
+
+static void
+em_outw(void)
+ {
+ asm volatile ("outw %w0, (%w1)"
+ : : "a" (context.vm.regs.eax),
+ "d" (context.vm.regs.edx));
+ }
+
+static void
+em_outl(void)
+ {
+ asm volatile ("outl %0, (%w1)"
+ : : "a" (context.vm.regs.eax),
+ "d" (context.vm.regs.edx));
+ }
+
+static int
+emulate(void)
+ {
+ unsigned char *insn;
+ struct
+ {
+ unsigned int size : 1;
+ unsigned int rep : 1;
+ } prefix = { 0, 0 };
+ int i = 0;
+
+ insn = (unsigned char *)((unsigned int)context.vm.regs.cs << 4);
+ insn += context.vm.regs.eip;
+
+ while (1)
+ {
+ if (insn[i] == 0x66)
+ {
+ prefix.size = 1 - prefix.size;
+ i++;
+ }
+ else if (insn[i] == 0xf3)
+ {
+ prefix.rep = 1;
+ i++;
+ }
+ else if (insn[i] == 0xf0 || insn[i] == 0xf2
+ || insn[i] == 0x26 || insn[i] == 0x2e
+ || insn[i] == 0x36 || insn[i] == 0x3e
+ || insn[i] == 0x64 || insn[i] == 0x65
+ || insn[i] == 0x67)
+ {
+ /* these prefixes are just ignored */
+ i++;
+ }
+ else if (insn[i] == 0x6c)
+ {
+ if (prefix.rep)
+ em_rep_ins(1);
+ else
+ em_ins(1);
+ i++;
+ break;
+ }
+ else if (insn[i] == 0x6d)
+ {
+ if (prefix.rep)
+ {
+ if (prefix.size)
+ em_rep_ins(4);
+ else
+ em_rep_ins(2);
+ }
+ else
+ {
+ if (prefix.size)
+ em_ins(4);
+ else
+ em_ins(2);
+ }
+ i++;
+ break;
+ }
+ else if (insn[i] == 0x6e)
+ {
+ if (prefix.rep)
+ em_rep_outs(1);
+ else
+ em_outs(1);
+ i++;
+ break;
+ }
+ else if (insn[i] == 0x6f)
+ {
+ if (prefix.rep)
+ {
+ if (prefix.size)
+ em_rep_outs(4);
+ else
+ em_rep_outs(2);
+ }
+ else
+ {
+ if (prefix.size)
+ em_outs(4);
+ else
+ em_outs(2);
+ }
+ i++;
+ break;
+ }
+ else if (insn[i] == 0xec)
+ {
+ em_inb();
+ i++;
+ break;
+ }
+ else if (insn[i] == 0xed)
+ {
+ if (prefix.size)
+ em_inl();
+ else
+ em_inw();
+ i++;
+ break;
+ }
+ else if (insn[i] == 0xee)
+ {
+ em_outb();
+ i++;
+ break;
+ }
+ else if (insn[i] == 0xef)
+ {
+ if (prefix.size)
+ em_outl();
+ else
+ em_outw();
+
+ i++;
+ break;
+ }
+ else
+ return 0;
+ }
+
+ context.vm.regs.eip += i;
+ return 1;
+ }
+
+
+/*
+ I don't know how to make sure I get the right vm86() from libc.
+ The one I want is syscall # 113 (vm86old() in libc 5, vm86() in glibc)
+ which should be declared as "int vm86(struct vm86_struct *);" in
+ <sys/vm86.h>.
+
+ This just does syscall 113 with inline asm, which should work
+ for both libc's (I hope).
+*/
+#if !defined(USE_LIBC_VM86)
+static int
+lrmi_vm86(struct vm86_struct *vm)
+ {
+ int r;
+#ifdef __PIC__
+ asm volatile (
+ "pushl %%ebx\n\t"
+ "movl %2, %%ebx\n\t"
+ "int $0x80\n\t"
+ "popl %%ebx"
+ : "=a" (r)
+ : "0" (113), "r" (vm));
+#else
+ asm volatile (
+ "int $0x80"
+ : "=a" (r)
+ : "0" (113), "b" (vm));
+#endif
+ return r;
+ }
+#else
+#define lrmi_vm86 vm86
+#endif
+
+
+static void
+debug_info(int vret)
+ {
+ int i;
+ unsigned char *p;
+
+ fputs("vm86() failed\n", stderr);
+ fprintf(stderr, "return = 0x%x\n", vret);
+ fprintf(stderr, "eax = 0x%08lx\n", context.vm.regs.eax);
+ fprintf(stderr, "ebx = 0x%08lx\n", context.vm.regs.ebx);
+ fprintf(stderr, "ecx = 0x%08lx\n", context.vm.regs.ecx);
+ fprintf(stderr, "edx = 0x%08lx\n", context.vm.regs.edx);
+ fprintf(stderr, "esi = 0x%08lx\n", context.vm.regs.esi);
+ fprintf(stderr, "edi = 0x%08lx\n", context.vm.regs.edi);
+ fprintf(stderr, "ebp = 0x%08lx\n", context.vm.regs.ebp);
+ fprintf(stderr, "eip = 0x%08lx\n", context.vm.regs.eip);
+ fprintf(stderr, "cs = 0x%04x\n", context.vm.regs.cs);
+ fprintf(stderr, "esp = 0x%08lx\n", context.vm.regs.esp);
+ fprintf(stderr, "ss = 0x%04x\n", context.vm.regs.ss);
+ fprintf(stderr, "ds = 0x%04x\n", context.vm.regs.ds);
+ fprintf(stderr, "es = 0x%04x\n", context.vm.regs.es);
+ fprintf(stderr, "fs = 0x%04x\n", context.vm.regs.fs);
+ fprintf(stderr, "gs = 0x%04x\n", context.vm.regs.gs);
+ fprintf(stderr, "eflags = 0x%08lx\n", context.vm.regs.eflags);
+
+ fputs("cs:ip = [ ", stderr);
+
+ p = (unsigned char *)((context.vm.regs.cs << 4) + (context.vm.regs.eip & 0xffff));
+
+ for (i = 0; i < 16; ++i)
+ fprintf(stderr, "%02x ", (unsigned int)p[i]);
+
+ fputs("]\n", stderr);
+ }
+
+
+static int
+run_vm86(void)
+ {
+ unsigned int vret;
+
+ while (1)
+ {
+ vret = lrmi_vm86(&context.vm);
+
+ if (VM86_TYPE(vret) == VM86_INTx)
+ {
+ unsigned int v = VM86_ARG(vret);
+
+ if (v == RETURN_TO_32_INT)
+ return 1;
+
+ pushw(context.vm.regs.eflags);
+ pushw(context.vm.regs.cs);
+ pushw(context.vm.regs.eip);
+
+ context.vm.regs.cs = get_int_seg(v);
+ context.vm.regs.eip = get_int_off(v);
+ context.vm.regs.eflags &= ~(VIF_MASK | TF_MASK);
+
+ continue;
+ }
+
+ if (VM86_TYPE(vret) != VM86_UNKNOWN)
+ break;
+
+ if (!emulate())
+ break;
+ }
+
+ debug_info(vret);
+
+ return 0;
+ }
+
+
+int
+LRMI_call(struct LRMI_regs *r)
+ {
+ unsigned int vret;
+
+ memset(&context.vm.regs, 0, sizeof(context.vm.regs));
+
+ set_regs(r);
+
+ context.vm.regs.cs = r->cs;
+ context.vm.regs.eip = r->ip;
+
+ if (r->ss == 0 && r->sp == 0)
+ {
+ context.vm.regs.ss = context.stack_seg;
+ context.vm.regs.esp = context.stack_off;
+ }
+ else
+ {
+ context.vm.regs.ss = r->ss;
+ context.vm.regs.esp = r->sp;
+ }
+
+ pushw(context.ret_seg);
+ pushw(context.ret_off);
+
+ vret = run_vm86();
+
+ get_regs(r);
+
+ return vret;
+ }
+
+
+int
+LRMI_int(int i, struct LRMI_regs *r)
+ {
+ unsigned int vret;
+ unsigned int seg, off;
+
+ seg = get_int_seg(i);
+ off = get_int_off(i);
+
+ /*
+ If the interrupt is in regular memory, it's probably
+ still pointing at a dos TSR (which is now gone).
+ */
+ if (seg < 0xa000 || (seg << 4) + off >= 0x100000)
+ {
+ fprintf(stderr, "Int 0x%x is not in rom (%04x:%04x)\n", i, seg, off);
+ return 0;
+ }
+
+ memset(&context.vm.regs, 0, sizeof(context.vm.regs));
+
+ set_regs(r);
+
+ context.vm.regs.cs = seg;
+ context.vm.regs.eip = off;
+
+ if (r->ss == 0 && r->sp == 0)
+ {
+ context.vm.regs.ss = context.stack_seg;
+ context.vm.regs.esp = context.stack_off;
+ }
+ else
+ {
+ context.vm.regs.ss = r->ss;
+ context.vm.regs.esp = r->sp;
+ }
+
+ pushw(DEFAULT_VM86_FLAGS);
+ pushw(context.ret_seg);
+ pushw(context.ret_off);
+
+ vret = run_vm86();
+
+ get_regs(r);
+
+ return vret;
+ }
+
diff --git a/lrmi.h b/lrmi.h
new file mode 100644
index 0000000..c9c1863
--- /dev/null
+++ b/lrmi.h
@@ -0,0 +1,85 @@
+/*
+Linux Real Mode Interface - A library of DPMI-like functions for Linux.
+
+Copyright (C) 1998 by Josh Vanderhoof
+
+You are free to distribute and modify this file, as long as you
+do not remove this copyright notice and clearly label modified
+versions as being modified.
+
+This software has NO WARRANTY. Use it at your own risk.
+*/
+
+#ifndef LRMI_H
+#define LRMI_H
+
+struct LRMI_regs
+ {
+ unsigned int edi;
+ unsigned int esi;
+ unsigned int ebp;
+ unsigned int reserved;
+ unsigned int ebx;
+ unsigned int edx;
+ unsigned int ecx;
+ unsigned int eax;
+ unsigned short int flags;
+ unsigned short int es;
+ unsigned short int ds;
+ unsigned short int fs;
+ unsigned short int gs;
+ unsigned short int ip;
+ unsigned short int cs;
+ unsigned short int sp;
+ unsigned short int ss;
+ };
+
+
+#ifndef LRMI_PREFIX
+#define LRMI_PREFIX LRMI_
+#endif
+
+#define LRMI_CONCAT2(a, b) a ## b
+#define LRMI_CONCAT(a, b) LRMI_CONCAT2(a, b)
+#define LRMI_MAKENAME(a) LRMI_CONCAT(LRMI_PREFIX, a)
+
+/*
+ Initialize
+ returns 1 if sucessful, 0 for failure
+*/
+#define LRMI_init LRMI_MAKENAME(init)
+int
+LRMI_init(void);
+
+/*
+ Simulate a 16 bit far call
+ returns 1 if sucessful, 0 for failure
+*/
+#define LRMI_call LRMI_MAKENAME(call)
+int
+LRMI_call(struct LRMI_regs *r);
+
+/*
+ Simulate a 16 bit interrupt
+ returns 1 if sucessful, 0 for failure
+*/
+#define LRMI_int LRMI_MAKENAME(int)
+int
+LRMI_int(int interrupt, struct LRMI_regs *r);
+
+/*
+ Allocate real mode memory
+ The returned block is paragraph (16 byte) aligned
+*/
+#define LRMI_alloc_real LRMI_MAKENAME(alloc_real)
+void *
+LRMI_alloc_real(int size);
+
+/*
+ Free real mode memory
+*/
+#define LRMI_free_real LRMI_MAKENAME(free_real)
+void
+LRMI_free_real(void *m);
+
+#endif
diff --git a/s3switch.1x b/s3switch.1x
new file mode 100644
index 0000000..2fcb3ea
--- /dev/null
+++ b/s3switch.1x
@@ -0,0 +1,62 @@
+.TH S3SWITCH 1x "S3 Savage Utilities"
+.SH NAME
+s3switch \- manage the output devices on an S3 Savage chip
+.SH SYNOPSIS
+.B s3switch
+[\-q] [crt] [lcd] [tv] [both] [ntscj|ntsc|pal]
+.SH DESCRIPTION
+The S3 Savage chips have the ability to send their output
+to a variety of different devices. The Savage3D can display to a standard
+CRT or to a TV. The Savage4 and Savage2000 can display to a standard
+CRT or through a digital interface to an LCD panel. The Savage/MX and
+Savage/IX can ship to any two of those three.
+.PP
+The
+.IR s3switch
+utility is used to select the devices to be used, and the format of the TV
+signal. When invoked with no parameters,
+.I s3switch
+displays the devices currently attached, and which of those devices are
+currently active. Supplying one or more of the words
+.I crt,
+.I lcd,
+or
+.I tv
+makes those devices the active devices. The word
+.I both
+is a shortcut for the combination of
+.I crt
+and
+.I lcd.
+.PP
+To change the format of the TV signal, specify either
+.I ntscj,
+.I ntsc
+or
+.I pal.
+These words will be silently ignored on systems where a TV is not attached
+or not possible.
+.PP
+The case of the keywords is unimportant;
+.I LCD
+and
+.I lcd
+are equivalent.
+.PP
+.IR S3switch
+always displays the current state of things just before it exits, unless the
+.I -q
+option is given, in which case, it operates quietly. Note that only the
+Savage4 and Savage2000 are able to detect the presence or absence of the
+various devices; the rest just assume everything is attached.
+.IR S3switch
+must be run as root.
+.PP
+.I S3switch
+will not allow you to activate devices which are not connected, on those
+chips that are able to detect this.
+.SH AUTHOR
+Tim Roberts (timr@probo.com)
+.SH "SEE ALSO"
+.BR X(1x),
+.BR Xserver(1x)
diff --git a/s3switch.c b/s3switch.c
new file mode 100644
index 0000000..615bf59
--- /dev/null
+++ b/s3switch.c
@@ -0,0 +1,454 @@
+// Simple utility to switch a Savage board between CRT/LCD devices.
+// T. N. Roberts, 99-Aug-26.
+
+// Linux x86 only.
+
+#include <stdio.h>
+#define extern
+#include <asm/io.h>
+#undef extern
+
+#include "lrmi.h"
+
+// Usage:
+// s3switch [-q] [crt|lcd|both]
+
+// Define the Savage chip classes. PCI id's stolen from xf86PciInfo.h
+
+#define PCI_CHIP_SAVAGE3D 0x8A20
+#define PCI_CHIP_SAVAGE3D_MV 0x8A21
+#define PCI_CHIP_SAVAGE4 0x8A22
+#define PCI_CHIP_SAVAGE2000 0x9102
+#define PCI_CHIP_PROSAVAGE_PM 0x8A25
+#define PCI_CHIP_PROSAVAGE_KM 0x8A26
+#define PCI_CHIP_SAVAGE_MX_MV 0x8c10
+#define PCI_CHIP_SAVAGE_MX 0x8c11
+#define PCI_CHIP_SAVAGE_IX_MV 0x8c12
+#define PCI_CHIP_SAVAGE_IX 0x8c13
+#define PCI_CHIP_TWISTERP 0x8d01
+#define PCI_CHIP_TWISTERK 0x8d02
+#define PCI_CHIP_PROSAVAGE_DDR 0x8d03
+#define PCI_CHIP_PROSAVAGE_DDRK 0x8d04
+#define PCI_CHIP_SUPSAV_MX128 0x8c22
+#define PCI_CHIP_SUPSAV_MX64 0x8c24
+#define PCI_CHIP_SUPSAV_MX64C 0x8c26
+#define PCI_CHIP_SUPSAV_IX128SDR 0x8c2a
+#define PCI_CHIP_SUPSAV_IX128DDR 0x8c2b
+#define PCI_CHIP_SUPSAV_IX64SDR 0x8c2c
+#define PCI_CHIP_SUPSAV_IX64DDR 0x8c2d
+#define PCI_CHIP_SUPSAV_IXCSDR 0x8c2e
+#define PCI_CHIP_SUPSAV_IXCDDR 0x8c2f
+
+enum {
+ S3_SAVAGE3D,
+ S3_SAVAGE4,
+ S3_SAVAGEMXIX,
+ S3_SAVAGE2000,
+ S3_PROSAVAGE,
+ S3_SUPERSAVAGE
+} ChipClass;
+
+// Define the device attachment bits. This is CR6D on the non-mobile
+// chips, and CR6B on the mobiles.
+
+// Savage3D does not support LCD, and the Savage4 does not support TV.
+
+#define CRT_ACTIVE 0x01
+#define LCD_ACTIVE 0x02
+#define TV_ACTIVE 0x04
+#define CRT_ATTACHED 0x10
+#define LCD_ATTACHED 0x20
+#define TV_ATTACHED 0x40
+#define DUO_ON 0x80
+
+static char * devices[] = {
+ " CRT", " LCD", " TV"
+};
+
+// Define the TV format bits in CR6B (non-mobile) or CRC0 (mobile).
+
+#define TV_FORMAT_MASK 0x0c
+#define TV_FORMAT_NTSCJ 0x00
+#define TV_FORMAT_NTSC 0x04
+#define TV_FORMAT_PAL 0x08
+
+// Global state:
+
+unsigned int gPCIid = 0;
+unsigned char jTvFormat = 0;
+unsigned char jDevices = 0;
+unsigned char cr79 = 0;
+
+void
+usage()
+{
+ puts( "Usage: s3switch [-q] [crt|lcd|both|tv] [ntsc|ntscj|pal]" );
+ puts( " -q requests quiet operation." );
+ puts( " crt, lcd and tv activates output to those devices. Several devices may be" );
+ puts( " specified. Only devices which are actually attached may be activated." );
+ puts( " both is a shortcut for 'crt lcd'." );
+ puts( " ntscj, ntsc and pal specify the video format for TV output." );
+ puts( " This is supported on Savage3D only.");
+ puts( " With no parameters, displays all devices currently attached and active.");
+}
+
+void
+IOAccess( int enable )
+{
+ /* Allow or disallow access to I/O ports. */
+
+ ioperm( 0x40, 4, enable );
+ ioperm( 0x61, 1, enable );
+ ioperm( 0x80, 1, enable );
+ ioperm( 0x3b0, 0x30, enable );
+}
+
+
+void
+fetch_bios_data()
+{
+ // Figure out what kind of Savage it is.
+
+ outb( 0x2d, 0x3d4 );
+ gPCIid = inb( 0x3d5 ) << 8;
+ outb( 0x2e, 0x3d4 );
+ gPCIid |= inb( 0x3d5 );
+
+ switch( gPCIid ) {
+ case PCI_CHIP_SAVAGE3D:
+ case PCI_CHIP_SAVAGE3D_MV:
+ ChipClass = S3_SAVAGE3D;
+ break;
+ case PCI_CHIP_SAVAGE4:
+ ChipClass = S3_SAVAGE4;
+ break;
+ case PCI_CHIP_SAVAGE2000:
+ ChipClass = S3_SAVAGE2000;
+ break;
+ case PCI_CHIP_PROSAVAGE_PM:
+ case PCI_CHIP_PROSAVAGE_KM:
+ case PCI_CHIP_TWISTERP:
+ case PCI_CHIP_TWISTERK:
+ case PCI_CHIP_PROSAVAGE_DDR:
+ case PCI_CHIP_PROSAVAGE_DDRK:
+ ChipClass = S3_PROSAVAGE;
+ break;
+ case PCI_CHIP_SAVAGE_MX_MV:
+ case PCI_CHIP_SAVAGE_MX:
+ case PCI_CHIP_SAVAGE_IX_MV:
+ case PCI_CHIP_SAVAGE_IX:
+ ChipClass = S3_SAVAGEMXIX;
+ break;
+ case PCI_CHIP_SUPSAV_MX128:
+ case PCI_CHIP_SUPSAV_MX64:
+ case PCI_CHIP_SUPSAV_MX64C:
+ case PCI_CHIP_SUPSAV_IX128SDR:
+ case PCI_CHIP_SUPSAV_IX128DDR:
+ case PCI_CHIP_SUPSAV_IX64SDR:
+ case PCI_CHIP_SUPSAV_IX64DDR:
+ case PCI_CHIP_SUPSAV_IXCSDR:
+ case PCI_CHIP_SUPSAV_IXCDDR:
+ ChipClass = S3_SUPERSAVAGE;
+ break;
+ default:
+ printf( "PCI id is not a recognized Savage: %04x\n", gPCIid );
+ exit(-1);
+ }
+
+ if( ChipClass == S3_SAVAGEMXIX )
+ {
+ outb( 0xc0, 0x3d4 );
+ jTvFormat = inb( 0x3d5 );
+ outb( 0x6b, 0x3d4 );
+ jDevices = inb( 0x3d5 );
+ }
+ else
+ {
+ outb( 0x6b, 0x3d4 );
+ jTvFormat = inb( 0x3d5 );
+ outb( 0x6d, 0x3d4 );
+ jDevices = inb( 0x3d5 );
+ }
+
+ outb( 0x79, 0x3d4 );
+ cr79 = inb( 0x3d5 );
+
+ //printf( "Device ID: %04x\n", gPCIid);
+
+ // The Savage4 and Savage2000 are the only chips which actually detect
+ // the presence of the devices. For the others, we just have to assume.
+
+ switch( ChipClass )
+ {
+ case S3_SAVAGE3D:
+ jDevices = (jDevices & 0x0f)
+ | CRT_ATTACHED
+ | TV_ATTACHED;
+ break;
+
+ case S3_SAVAGE4:
+ case S3_SAVAGE2000:
+ /* These two get it right. */
+ break;
+
+ case S3_SAVAGEMXIX:
+ case S3_PROSAVAGE:
+ case S3_SUPERSAVAGE:
+ default:
+ jDevices = (jDevices & 0x0f)
+ | CRT_ATTACHED
+ | TV_ATTACHED
+ | LCD_ATTACHED;
+ break;
+ }
+}
+
+
+unsigned short
+set_active_device( int iDevice )
+{
+ struct LRMI_regs r;
+ int iResult = 0;
+
+ if (!LRMI_init())
+ return 1;
+
+ /* Go set the active device. */
+
+ memset( &r, 0, sizeof(r) );
+
+ r.eax = 0x4f14; // S3 extended functions
+ r.ebx = 0x0003; // set active device
+ r.ecx = iDevice;
+
+ if( ChipClass == S3_SAVAGEMXIX )
+ r.ecx |= DUO_ON;
+
+ iResult = LRMI_int( 0x10, &r );
+
+ if( !iResult )
+ {
+ fprintf( stderr, "Could not set device (vm86 failure)\n" );
+ return 1;
+ }
+
+ if ( (r.eax & 0xffff) != 0x4f )
+ {
+ fprintf( stderr, "BIOS returned error code.\n" );
+ return 1;
+ }
+
+ return 0;
+}
+
+
+
+unsigned short
+set_tv_state( int state )
+{
+ struct LRMI_regs r;
+ int iResult = 0;
+
+ if (!LRMI_init())
+ return 1;
+
+ /* And go set the TV state. */
+
+ memset( &r, 0, sizeof(r) );
+
+ r.eax = 0x4f14; // S3 extended functions
+ r.ebx = 0x0007; // set tv state
+ r.ecx = state;
+ r.edx = TV_FORMAT_MASK;
+
+ iResult = LRMI_int( 0x10, &r );
+
+ if( !iResult )
+ {
+ fprintf( stderr, "Could not set TV state (vm86 failure)\n" );
+ return 1;
+ }
+
+ if ( (r.eax & 0xffff) != 0x4f )
+ {
+ fprintf( stderr, "BIOS returned error code.\n" );
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+print_current_state()
+{
+ int i;
+
+ printf( "Devices attached: " );
+
+ if( !(jDevices & 0x70) )
+ {
+ // How can this be?
+ printf( "none" );
+ }
+ else
+ for( i = 0; i < 3; i++ )
+ if( jDevices & (0x10 << i) )
+ printf( devices[i] );
+
+ printf( "\nDevices active: " );
+
+ if( !(jDevices & 0x07) )
+ {
+ // How can this be?
+ printf( "none\n" );
+ }
+ else
+ for( i = 0; i < 3; i++ )
+ if( jDevices & (0x01 << i) )
+ printf( devices[i] );
+
+ if( jDevices & TV_ATTACHED )
+ {
+ static char * szTV[] = { "NTSC-J", "NTSC", "PAL" };
+
+ printf(
+ "\nCurrent TV format is %s",
+ szTV[(jTvFormat & TV_FORMAT_MASK) >> 2]
+ );
+ }
+
+ printf( "\n" );
+}
+
+
+void
+set_new_state( int newstate )
+{
+ // We should prohibit TV on Savage4.
+
+ if( ((jDevices >> 4) & newstate) != newstate )
+ {
+ fprintf( stderr, "You attempted to activate a device which is not connected.\n" );
+ // Alternatively, quiet = 0, return.
+ print_current_state();
+ exit( -2 );
+ }
+
+ set_active_device( newstate );
+
+ // If the LCD state changed, we need to adjust cr79 in Savage4.
+ // These values are somewhat magical, and are set by the X server.
+
+ if( (ChipClass == S3_SAVAGE4) || (ChipClass == S3_SAVAGE2000) )
+ {
+ if( (jDevices & LCD_ACTIVE) && !(newstate & LCD_ACTIVE) )
+ {
+ // The LCD was alive and now it isn't. We can increase cr79.
+
+ if( (cr79 == 5) || (cr79 == 8) )
+ {
+ cr79 = (cr79 == 5) ? 8 : 0x0e;
+ ioperm( 0x3d4, 2, 1 );
+ outw( (cr79 << 8) | 0x79, 0x3d4 );
+ ioperm( 0x3d4, 2, 0 );
+ }
+ }
+ else if( !(jDevices & LCD_ACTIVE) && (newstate & LCD_ACTIVE) )
+ {
+ // The LCD was off and now it's on. We must cut back cr79.
+
+ if( (cr79 == 8) || (cr79 == 0xe) )
+ {
+ cr79 = (cr79 == 8) ? 5 : 8;
+ ioperm( 0x3d4, 2, 1 );
+ outw( (cr79 << 8) | 0x79, 0x3d4 );
+ ioperm( 0x3d4, 2, 0 );
+ }
+ }
+ }
+
+ fetch_bios_data();
+
+ return;
+}
+
+
+void
+set_new_tvstate( int tvstate )
+{
+ if( ChipClass == S3_SAVAGE4 )
+ return;
+
+ set_tv_state( tvstate );
+
+ fetch_bios_data();
+
+ return;
+}
+
+
+int
+main( int argc, char ** argv )
+{
+ int quiet = 0;
+ int newstate = 0;
+ int newtv = 0;
+
+ if( geteuid() != 0 )
+ {
+ fprintf( stderr, "s3switch must be setuid root.\n" );
+ exit( -1 );
+ }
+
+ // Scan through the argument list. We do very primitive checking here.
+
+ while( *++argv )
+ {
+ if( strcmp( *argv, "-q" ) == 0 )
+ quiet++;
+ else if( strcasecmp( *argv, "crt" ) == 0 )
+ newstate |= CRT_ACTIVE;
+ else if( strcasecmp( *argv, "lcd" ) == 0 )
+ newstate |= LCD_ACTIVE;
+ else if( strcasecmp( *argv, "both" ) == 0 )
+ newstate |= CRT_ACTIVE | LCD_ACTIVE;
+ else if( strcasecmp( *argv, "tv" ) == 0 )
+ newstate |= TV_ACTIVE;
+ else if( strcasecmp( *argv, "ntsc-j" ) == 0 )
+ newtv = TV_FORMAT_NTSCJ;
+ else if( strcasecmp( *argv, "ntscj" ) == 0 )
+ newtv = TV_FORMAT_NTSCJ;
+ else if( strcasecmp( *argv, "ntsc" ) == 0 )
+ newtv = TV_FORMAT_NTSC;
+ else if( strcasecmp( *argv, "pal" ) == 0 )
+ newtv = TV_FORMAT_PAL;
+ else if( strcmp( *argv, "-h" ) == 0 )
+ {
+ usage();
+ exit( 0 );
+ }
+ else
+ {
+ fprintf( stderr, "Unknown argument: %s\n", *argv );
+ usage();
+ exit( -1 );
+ }
+ }
+
+ IOAccess( 1 );
+
+ fetch_bios_data();
+
+ if( newtv )
+ set_new_tvstate( newtv );
+
+ if( newstate )
+ set_new_state( newstate );
+
+ if( !quiet )
+ print_current_state( );
+
+ IOAccess( 0 );
+
+ return 0;
+}