diff options
author | Tim Roberts <timr@probo.com> | 2002-09-12 00:26:00 +0200 |
---|---|---|
committer | Tormod Volden <debian.tormod@gmail.com> | 2011-11-06 20:46:20 +0100 |
commit | 31999fea5bca32baa7cbe38a54bd88d0888939ab (patch) | |
tree | f4d7e8d069a1bec81cc8fba7abb4d72ae03c5885 |
Imported Tim Roberts' original codes3ssrc.zip
From http://www.probo.com/timr/s3/s3ssrc.zip
md5sum 1328b070343ac79c5ed4c613a1113754 s3ssrc.zip
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | lrmi.c | 883 | ||||
-rw-r--r-- | lrmi.h | 85 | ||||
-rw-r--r-- | s3switch.1x | 62 | ||||
-rw-r--r-- | s3switch.c | 454 |
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 @@ -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; + } + @@ -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; +} |