/* History:390,1 */ This is the file internals.doc Contents: * Methods that go32 uses to perform the DOS extender function. * How to add new graphics card support. Note: This file includes PC graphics characters in some illustrations. It is intended to be viewed via PC or PC compatible printer. If you are viewing it from Unix and the illustrations look strange, this is why. Methods: Go32 contains three basic parts: * a real-mode control program (code/data segments) * a 16-bit protected mode interface section * a 32-bit flat program arena, loaded from a.out file The interface section provides Turbo-C routines that acces memory in the 32-bit arena, as well as a method for executing code in protected mode. Initialization: Control.c is the starting point for the extender. main() is here. This is where all the system tables are created, like the GDT and TSSs. TABLES.ASM contains the stubs for the exception handlers, and the exception tasks. The IDT is mass-loaded by main() to point to these stubs, then the oddball exceptions are moved (ivec7, page fault handler, etc). control assist functions: utils.c, doutils.asm All control assist functions use linear/physical addresses, not segment addresses! The control assist functions allow the controller to read/write memory from real mode into the 32-bit arena. The available functions are: word32 peek32(vaddr) poke32(vaddr, word32) word16 peek16(vaddr) poke16(vaddr, word16) word8 peek8(vaddr) poke8(vaddr, word8) memget(vaddr, void *, length) memput(vaddr, void *, length) zero32(vaddr) /* always 4K regions */ The main focal point for process control is a TSS structure with some additional fields at the end. These additional fields record information such as CR2, the exception number, a temporary stack, and other information deduced by the extender. There are many TSSs used: * c_tss is the control tasks. This is the "real mode" task that is initially switched to as part of entering protected mode. A switch to this task returns you to real mode. * a_tss is the arena's task state. By using the fields in this structure, the extender can get or change register contents. When the arena task causes an exception, the state of the task is recorded here for the extender to use in servicing the exception. * o_tss is the task structure used by the utilities ("other" routines). * p_tss is the task switched to when a page fault occurs. * f_tss is the task used by the page fault handler when it calls the utilities. This is in case the utilites cause a page fault. * i_tss is the task most exceptions switch to. Exception 7 is redirected to a protected mode handler if a 387 is present. Each exception has a stub (in tables.asm) that saves the exception number and jumps to this task. This task just un-does the effects of the interrupt, so the TSS points to the exception and not the exception handler, and then returns to real mode. The variable tss_ptr points to the currently active TSS. This is the one the debugger uses when exceptions happen. Running protected mode code: MSWITCH.ASM is the gateway into the protected mode arena. *ALL* accesses to protected mode go into go32() and come out through it. A20 switching is also handled here. go32() does a number of functions: * Set the active task gate in the GDT to refer to tss_ptr. * Disable interrupts for all tasks except a_tss. * Clear all the TaskBusy bits for all tasks, to prevent exceptions. * Clear the exception flag (set when an exception occurs). * save the real mode stack state. * Disable sensitive interrupts (like the hard drive). IRQ0-7 remain enabled. * Make sure A20 is enabled (a20gate was a stupid idea). * Set up the GDT and IDT registers. * Go into protected mode. * Load the debug registers from _dr[]. * Enable the paging hardware * Load the 387 state from the structure "npx", if the state had been stored there (by an exception handler or the "npx" instruction). * Load the task register to point to c_tss * Switch to tss_ptr's task . . . wait for switch back to c_tss . . . * Save DR6 in _dr6 (debug status). * Reset IDT and stack * Re-enable interrupts * Handle certain time-critical exceptions, like timer tick and COM ports. Keyboard and NPX interrupts, as well as all other exceptions, are handled by exception_handler(). * return to caller. The basic flow of control looks like this (in to top, out from bottom): ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Start - Real Mode º ÈÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍͼ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÉÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍÏÍÍÍÍÍÍÍ» ³ º Protected Mode, c_tss º ³ ÈÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÑÍÍͼ ³ ³ ÀÄÄÄÄÄÄÄÄ¿ ³ ÉÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÍÍÍ» ÉÍÏÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ³ º tss_ptr, a_tss º º tss_ptr, o_tss/f_tss º ³ ÈÍÍÍÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÑÍÍͼ ÈÍÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ³ ³ ³ ³ ³ ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍ» ÉÍÍÍÏÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÍÍ» ³ º Exception º º Page Fault, p_tss º ³ ÈÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍͼ ÈÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÑÍÍÍÍÍÍͼ ³ ÉÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÍÍÍ» ³ ÉÍÍÏÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ³ º i_tss º ³ º Graphics handler º ³ ÈÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍͼ ³ ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÑͼ ³ ÀÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÙ ÉÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍ» º Back to Real Mode º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ GDT entries: * mandatory zero entry * GDT * IDT * real-mode code segment (for utilities) * real-mode data segment (for utilities) * real-mode code, but 32-bit (unused) * real-mode data, but 32-bit (unused) * core. 32-bits, mapped 1:1 with first 1M of memory. Used to access screen. * arena code. 32-bit, starts at 0x10000000. * arena data. 32-bit, starts at 0x10000000. * c_tss task segment * a_tss task segment - changes to reflect what tss_ptr points to * p_tss task segment * i_tss task segment * graphics driver routines * real code 32-bits, was used for NPX routines (unused) Mappings: * First 1M mapped 1:1 to linear address 0x0000000 * Arena segments start at 0x10000000. All utilities must be adjusted to reflect this by adding the constant ARENA to addresses. * Symbol space is at 0xa000000 (used by debug32) * Core (0-1M) remapped to 0xF0000000 (0xE0000000 to arena) * VGA 256c paging at 0xE0000000 (0xD0000000 to arena) Three 1M pages: 0xD0000000 - 0xD00FFFFF - read/write 0xD0100000 - 0xD01FFFFF - read only 0xD0200000 - 0xD02FFFFF - write only Page management: Valloc.c keeps track of what physical pages are available. Pages are accessed via valloc() and vfree(), which use page *numbers* (not addresses). Up to 128M of RAM can be managed by this routine. If valloc() is called and there aren't any free pages, page_out() is called to free one up. The application has the option of choosing memory from the lower 640K or above 1M. The page directory and page tables are kept in low memory so the real mode functions can access them without going into protected mode. The page table entries include extra information about the status of a given page, whether it is swappable, swapped, or uninitialized. Paging.c keeps track of the page tables and directory. It reads the a.out file and sets up various areas of memory (text, data, stack, etc). When page faults occur, page_in() pages in memory from disk, swap, or available memory (for bss and sbrk()). It detects accesses outside the designated areas to flag a protection violation. Page_out() selects a present page and swaps it to the swap file, to make space for an incoming page. Dalloc.c keeps track of the disk-based swap space, through dalloc() and dfree(). Up to 128M of swap space can be used. A20: A20 is enabled if it was disabled, and never disabled by the extender. To determine if it is disabled, the extender compares 0:0 with FFFF:0010 to see if they map to the same physical memory. Exceptions: All exceptions are handled by exception_handler() in expnhdlr.c. This function returns 1 if a fault happens, or 0 if it handles the exception. Control returns to the arena after an exception is handled. Unhandled exceptions return you to the debugger or to DOS. Changes to the DOS INT 21H calls: brk() and sbrk() replace the DOS memory management vector: int 21,AH=4A (change memory allocation) AL=0 brk(ebx) returns old brk in eax AL=1 sbrk(ebx) returns old brk in eax Some system calls are handled by the extender directly, instead of through DOS interrupts: int 21,AH=FF - turbo assist. Func in AL, parms (ebx,ecx,edx) ret eax 1: creat 2: open 3: fstat 4: gettimeofday 5: settimeofdat 6: stat 7: system int 10, AH=FF - set video mode 0: 80x25 text 1: default text 2: text CX cols by DX rows 3: biggest text 4: 320x200 graphics 5: default graphics 6: graphics CX width by DX height 7: biggest non-interlaced graphics 8: biggest graphics 80387: NPX.ASM has all the NPX code (except displaying it, that's in debug.c). When a numeric exception happens, the 387 state is stored in [_npx], and _npx_stored is set to 1. go32() will restore the 387 from [_npx] if it is going to run a_tss (and if the state is stored there). Most of the time, the 387 state remains in the 387 and _npx_stored remains 0. The debugger can also move the 387 state from the 387 to [_npx] through save_npx() if it needs to look at it's state (or fix it, which it doesn't yet do). Exception 7 (device not available) occurs the first time you use the 387 after a task switch. It just resets the task switch flag in CR0 and resumes. This vector points to the real mode handler if the 387 is not installed, to detect attempts to use the 387. Exception 0x75 (IRQ13) is the numeric exception vector. Currently, only divide-by-zero and invalid operation are unmasked. This includes sqrt(-1), ln(-1), npx stack faults, acos(2), etc. Debugging: There are seven debug registers, and seven variables (_dr0.._dr7, or _dr[0..7]) that control them. go32() loads the debug registers from these variables going into protected mode, and clears them coming out. Debug registers point to linear, not segment, addresses, so ad 0x10000000 to them for arena addresses. syms.c handles all symbol management, including register names and expression parsing. Symbols are stored in virtual memory beginning at 0xa0000000. unassmble.c handles unassembling machine instructions. It's very table driven, and handles all known (I hope) 386/387 instructions and address modes. debug.c is the central point for debuggin instructions. The main loop for the debugger is here. The utility function will longjump() to the beginning of the debugger() loop (if available) if they detect an exception. Graphics Card support: The files graphics.c and grprot.asm control the graphics handling. The system allows an external graphics driver to be loaded and used instead of the built-in VGA graphics driver. The Graphics Driver: Go32 includes the standard VGA driver built-in, as well as in an external driver. To select an external driver, enter a DOS command like: C> set go32=driver c:\go32\drivers\tseng4k.grd tw 132 th 43 gw 800 gh 600 This says that the driver is "c:\go32\drivers\tseng4k.grd", that the default text mode is 132x43, and that the default graphics mode is 800x600. The driver defines the resolutions for all the other modes. The parameters may be listed in any order, or omitted: C> set go32=driver c:\go32\drivers\tseng4k.grd gh 600 gw 800 There is also an "ansi" parameter that may be added to use an ansi screen driver to use colors while debugging. For a listing of what the various modes are, see any of the following: * listing earlier in this document * code for the drivers * include/graphics.h Example drivers are in vga.asm, tseng4k.asm, and tseng3k.asm. The first two words point to the two routines a driver provides. The next word indicates if "split page" is available (see below). The next four words are the default text and graphics sizes. The "split page" feature of some vga's (like the TSENG, unlike PS/2) allow one bank to be used for reading and another for writing, so reading 0xa000:0x0400 talks to one byte in VRAM, and writing 0xa000:0x0400 talks to a different byte. If your card supports this, set the split-bank word to 1. If not, set it to 0. You can return anything for PS/2's because they don't have enough memory to need page swapping, so can't really tell that you don't have that feature. GRAPHICS.C installs and calls the graphics driver for mode changes. The mode change function is pointed to by the first pointer (note: the pointers are only read when the file is installed). It takes the mode in AX, and the size (when required) in CX and DX. It should return the new size in CX and DX. GRPROT.ASM handles the actual page faults. It uses a page table's PRESENT bits to determine where the application can read/write to without having to reconfigure the graphics card. There are two states the graphics system can be in: 1: rw Access to 0xd0000000 - 0xd00fffff are allowed. Reads and writes to the same address refer to the same physical byte of video memory ÚÄÄÄRÄÄÄ¿ 0xD00xxxxx ÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ video ram ÀÄÄÄWÄÄÄÙ 0xD00xxxxx 2: r_w Reads to 0xd0100000 - 0xd01fffff and writes to 0xd0200000 - 0xd02fffff are allowed. The card is configured to read from one bank and write to another. This is "split bank" mode, and is used for fast blits across the screen. ÚÄÄÄRÄÄÄ¿ 0xD01xxxxxx ÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ video ram ÀÄÄÄWÄÄÄÙ 0xD02xxxxxx When grprot determines that the VGA has to be reprogrammed, it calls the function pointed to by the second pointer (in protected mode), passing the read page in AH and the write page in AL. That's it! All knowlegde of card specifics are contained in the drivers, so libgr.a and your programs don't have to be recompiled! If you can't manage to get your card to work, I may be persuaded to add it for you (as long as you provide technical manuals!). If you add a card, please send me the sources for the driver so I can integrate it into future releases!