provided code
This commit is contained in:
508
specs/freevga/vga/vgareg.htm
Normal file
508
specs/freevga/vga/vgareg.htm
Normal file
@@ -0,0 +1,508 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
|
||||
<META NAME="Author" CONTENT="Joshua Neal">
|
||||
<META NAME="Description" CONTENT="Pure VGA/SVGA hardware programming (registers, identification, and otherlow-level stuff.)">
|
||||
<META NAME="KeyWords" CONTENT="VGA SVGA hardware video programming">
|
||||
<TITLE>VGA/SVGA Video Programming--Accessing the VGA Registers</TITLE>
|
||||
</HEAD>
|
||||
<BODY>
|
||||
|
||||
<CENTER><A HREF="../home.htm">Home</A> <A HREF="#general">Intro</A> <A HREF="#general">Advice</A>
|
||||
<A HREF="#fudge">Fudge</A> <A HREF="#paranoia">Paranoia</A> <A HREF="#external">External</A>
|
||||
<A HREF="#indexed">Indexed</A> <A HREF="#attribute">Attribute</A> <A HREF="#color">Color</A>
|
||||
<A HREF="#binary">Binary</A> <A HREF="#example">Example</A> <A HREF="#bitfields">Masking</A>
|
||||
<A HREF="vga.htm#register">Back</A>
|
||||
<HR WIDTH="100%"><B>Hardware Level VGA and SVGA Video Programming Information
|
||||
Page</B></CENTER>
|
||||
|
||||
<CENTER>Accessing the VGA Registers
|
||||
<HR WIDTH="100%"></CENTER>
|
||||
|
||||
<UL>
|
||||
<LI>
|
||||
<A HREF="#intro">Introduction</A> -- provides a general overview of accessing
|
||||
the VGA registers.</LI>
|
||||
|
||||
<LI>
|
||||
<A HREF="#general">General Advice</A> -- basic guidelines for use when
|
||||
accessing VGA registers.</LI>
|
||||
|
||||
<LI>
|
||||
<A HREF="#fudge">I/O Fudge Factor</A> -- discusses delays between I/O accesses.</LI>
|
||||
|
||||
<LI>
|
||||
<A HREF="#paranoia">Paranoia</A> -- discusses making code more robust.</LI>
|
||||
|
||||
<LI>
|
||||
<A HREF="#external">Accessing the External Registers</A> -- details and
|
||||
guidelines for accessing these registers.</LI>
|
||||
|
||||
<LI>
|
||||
<A HREF="#indexed">Accessing the Sequencer, Graphics, and CRT Controller
|
||||
Registers</A> -- details and guidelines for accessing these registers,
|
||||
including step-by-step instructions.</LI>
|
||||
|
||||
<LI>
|
||||
<A HREF="#attribute">Accessing the Attribute Registers</A> -- details and
|
||||
guidelines for accessing this register, including step-by-step instructions.</LI>
|
||||
|
||||
<LI>
|
||||
<A HREF="#color">Accessing the Color Registers</A> -- details and guidelines
|
||||
for accessing this register, including step-by-step instructions.</LI>
|
||||
|
||||
<LI>
|
||||
<A HREF="#binary">Binary Operations</A> -- details on the operation of
|
||||
the logical operators OR, AND, and XOR.</LI>
|
||||
|
||||
<LI>
|
||||
<A HREF="#example">Example Register</A> -- an example register selected
|
||||
to demonstrate both the format they will be presented in and how fields
|
||||
work.</LI>
|
||||
|
||||
<LI>
|
||||
<A HREF="#bitfields">Masking Bit-Fields</A> -- details on changing specific
|
||||
fields within registers using the logical operators.</LI>
|
||||
</UL>
|
||||
<A NAME="intro"></A><B>Introduction</B>
|
||||
<BR> This section discusses methods
|
||||
of manipulating the particular registers present in VGA hardware. Depending
|
||||
upon which register one is accessing, the method of accessing them is different
|
||||
and sometimes difficult to understand. The VGA has many more registers
|
||||
than it has I/O ports, thus it must provide a way to re-use or multiplex
|
||||
many registers onto a relatively small number of ports. All of the VGA
|
||||
ports are accessed by inputting and outputting bytes to I/O ports; however,
|
||||
in many cases it is necessary to perform additional steps to ready the
|
||||
VGA adapter for reading and writing data. Port addresses are given at their
|
||||
hexadecimal address, such as 3C2h.
|
||||
|
||||
<P><A NAME="general"></A><B>General Advice</B>
|
||||
<BR><B> </B>If a program takes
|
||||
control of the video card and changes its state, it is considered good
|
||||
programming practice to keep track of the original values of any register
|
||||
it changes such that upon termination (normal or abnormal) it can write
|
||||
them back to the hardware to restore the state. Anyone who has seen a graphics
|
||||
application abort in the middle of a graphics screen knows how annoying
|
||||
this can be. Almost all of the VGA registers can be saved and restored
|
||||
in this fashion. In addition when changing only a particular field of a
|
||||
register, the value of the register should be read and the byte should
|
||||
be masked so that only the field one is trying to change is actually changed.
|
||||
|
||||
<P><A NAME="fudge"></A><B>I/O Fudge Factor</B>
|
||||
<BR> Often a hardware device
|
||||
is not capable handling I/O accesses as fast as the processor can issue
|
||||
them. In this case, a program must provide adequate delay between I/O accesses
|
||||
to the same device. While many modern chipsets provide this delay in hardware,
|
||||
there are still many implementations in existence that do not provide this
|
||||
delay. If you are attempting to write programs for the largest possible
|
||||
variety of hardware configurations, then it is necessary to know the amount
|
||||
of delay necessary. Unfortunately, this delay is not often specified, and
|
||||
varies from one VGA implementation to another. In the interest of performance
|
||||
it is ideal to keep this delay to the minimum necessary. In the interest
|
||||
of compatibility it is necessary to implement a delay independent of clock
|
||||
speed. (Faster processors are continuously being developed, and also a
|
||||
user may change clock speed dynamically via the Turbo button on their case.)
|
||||
|
||||
<P><A NAME="paranoia"></A><B>Paranoia</B>
|
||||
<BR> If one wishes to be extra
|
||||
cautious when writing to registers, after writing to a register one can
|
||||
read the value back and compare it with the original value. If they differ
|
||||
it may mean that the VGA hardware has a stuck bit in one its registers,
|
||||
that you are attempting to modify a locked or unsupported register, or
|
||||
that you are not providing enough delay between I/O accesses. As long as
|
||||
reading the register twice doesn't have any unintended side effects, when
|
||||
reading a registers value, one can read the register twice and compare
|
||||
the values read, after masking out any fields that may change without CPU
|
||||
intervention. If the values read back are different it may mean that you
|
||||
are not providing enough delay between I/O accesses, that the hardware
|
||||
is malfunctioning, or are reading the wrong register or field. Other problems
|
||||
that these techniques can address are noise on the I/O bus due to faulty
|
||||
hardware, dirty contacts, or even sunspots! When perform I/O operations
|
||||
and these checks fail, try repeating the operation, possibly with increased
|
||||
I/O delay time. By providing extra robustness, I have found that my own
|
||||
programs will work properly on hardware that causes less robust programs
|
||||
to fail.
|
||||
|
||||
<P><A NAME="external"></A><B>Accessing the External Registers</B>
|
||||
<BR> The external registers are
|
||||
the easiest to program, because they each have their own separate I/O address.
|
||||
Reading and writing to them is as simple as inputting and outputting bytes
|
||||
to their respective port address. Note, however some, such as the Miscellaneous
|
||||
Output Register is written at port 3C2h, but is read at port 3CCh. The
|
||||
reason for this is for backwards compatibility with the EGA and previous
|
||||
adapters. Many registers in the EGA were write only, and thus the designers
|
||||
placed read-only registers at the same location as write-only ones. However,
|
||||
the biggest complaint programmers had with the EGA was the inability to
|
||||
read the EGA's video state and thus in the design of the VGA most of these
|
||||
write-only registers were changed to read/write registers. However, for
|
||||
backwards compatibility, the read-only register had to remain at 3C2h,
|
||||
so they used a different port.
|
||||
|
||||
<P><A NAME="indexed"></A><B>Accessing the Sequencer, Graphics, and CRT
|
||||
Controller Registers</B>
|
||||
<BR> These registers are accessed
|
||||
in an indexed fashion. Each of the three have two unique read/write ports
|
||||
assigned to them. The first port is the Address Register for the group.
|
||||
The other is the Data Register for the group. By writing a byte to the
|
||||
Address Register equal to the index of the particular sub-register you
|
||||
wish to access, one can address the data pointed to by that index by reading
|
||||
and writing the Data Register. The current value of the index can be read
|
||||
by reading the Address Register. It is best to save this value and restore
|
||||
it after writing data, particularly so in an interrupt routine because
|
||||
the interrupted process may be in the middle of writing to the same register
|
||||
when the interrupt occurred. To read and write a data register in one of
|
||||
these register groups perform the following procedure:
|
||||
<OL>
|
||||
<LI>
|
||||
Input the value of the Address Register and save it for step 6</LI>
|
||||
|
||||
<LI>
|
||||
Output the index of the desired Data Register to the Address Register.</LI>
|
||||
|
||||
<LI>
|
||||
Read the value of the Data Register and save it for later restoration upon
|
||||
termination, if needed.</LI>
|
||||
|
||||
<LI>
|
||||
If writing, modify the value read in step 3, making sure to mask off bits
|
||||
not being modified.</LI>
|
||||
|
||||
<LI>
|
||||
If writing, write the new value from step 4 to the Data register.</LI>
|
||||
|
||||
<LI>
|
||||
Write the value of Address register saved in step 1 to the Address Register.</LI>
|
||||
</OL>
|
||||
If you are paranoid, then you
|
||||
might want to read back and compare the bytes written in step 2, 5, and
|
||||
6 as in the <A HREF="#paranoia">Paranoia</A> section above. Note that certain
|
||||
CRTC registers can be protected from read or write access for compatibility
|
||||
with programs written prior to the VGA's existence. This protection is
|
||||
controlled via the <A HREF="crtcreg.htm#03">Enable Vertical Retrace Access</A>
|
||||
and <A HREF="crtcreg.htm#11">CRTC Registers Protect Enable</A> fields.
|
||||
Ensuring that access is not prevented even if your card does not normally
|
||||
protect these registers makes your
|
||||
|
||||
<P><A NAME="attribute"></A><B>Accessing the Attribute Registers</B>
|
||||
<BR> The attribute registers
|
||||
are also accessed in an indexed fashion, albeit in a more confusing way.
|
||||
The address register is read and written via port 3C0h. The data register
|
||||
is written to port 3C0h and read from port 3C1h. The index and the data
|
||||
are written to the same port, one after another. A flip-flop inside the
|
||||
card keeps track of whether the next write will be handled is an index
|
||||
or data. Because there is no standard method of determining the state of
|
||||
this flip-flop, the ability to reset the flip-flop such that the next write
|
||||
will be handled as an index is provided. This is accomplished by reading
|
||||
the Input Status #1 Register (normally port 3DAh) (the data received is
|
||||
not important.) This can cause problems with interrupts because there is
|
||||
no standard way to find out what the state of the flip-flop is; therefore
|
||||
interrupt routines require special card when reading this register. (Especially
|
||||
since the Input Status #1 Register's purpose is to determine whether a
|
||||
horizontal or vertical retrace is in progress, something likely to be read
|
||||
by an interrupt routine that deals with the display.) If an interrupt were
|
||||
to read 3DAh in the middle of writing to an address/data pair, then the
|
||||
flip-flop would be reset and the data would be written to the address register
|
||||
instead. Any further writes would also be handled incorrectly and thus
|
||||
major corruption of the registers could occur. To read and write an data
|
||||
register in the attribute register group, perform the following procedure:
|
||||
<OL>
|
||||
<LI>
|
||||
Input a value from the Input Status #1 Register (normally port 3DAh) and
|
||||
discard it.</LI>
|
||||
|
||||
<LI>
|
||||
Read the value of the Address/Data Register and save it for step 7.</LI>
|
||||
|
||||
<LI>
|
||||
Output the index of the desired Data Register to the Address/Data Register</LI>
|
||||
|
||||
<LI>
|
||||
Read the value of the Data Register and save it for later restoration upon
|
||||
termination, if needed.</LI>
|
||||
|
||||
<LI>
|
||||
If writing, modify the value read in step 4, making sure to mask off bits
|
||||
not being modified.</LI>
|
||||
|
||||
<LI>
|
||||
If writing, write the new value from step 5 to the Address/Data register.</LI>
|
||||
|
||||
<LI>
|
||||
Write the value of Address register saved in step 1 to the Address/Data
|
||||
Register.</LI>
|
||||
|
||||
<LI>
|
||||
If you wish to leave the register waiting for an index, input a value from
|
||||
the Input Status #1 Register (normally port 3DAh) and discard it.</LI>
|
||||
</OL>
|
||||
If you have control over interrupts,
|
||||
then you can disable interrupts while in the middle of writing to the register.
|
||||
If not, then you may be able to implement a critical section where you
|
||||
use a byte in memory as a flag whether it is safe to modify the attribute
|
||||
registers and have your interrupt routine honor this. And again, it pays
|
||||
to be paranoid. Resetting the flip-flop even though it <B>should</B> be
|
||||
in the reset state already helps prevent catastrophic problems. Also, you
|
||||
might want to read back and compare the bytes written in step 3, 6, and
|
||||
7 as in the <A HREF="#paranoia">Paranoia</A> section above.
|
||||
<BR> On the IBM VGA implementation,
|
||||
an undocumented register (CRTC Index=24h, bit 7) can be read to determine
|
||||
the status of the flip-flop (0=address,1=data) and many VGA compatible
|
||||
chipsets duplicate this behavior, but it is not guaranteed. However, it
|
||||
is a simple matter to determine if this is the case. Also, some SVGA chipsets
|
||||
provide the ability to access the attribute registers in the same fashion
|
||||
as the CRT, Sequencer, and Graphics controllers. Because this functionality
|
||||
is vendor specific it is really only useful when programming for that particular
|
||||
chipset. To determine if this undocumented bit is supported, perform the
|
||||
following procedure:
|
||||
<OL>
|
||||
<LI>
|
||||
Input a value from the Input Status #1 Register (normally port 3DAh) and
|
||||
discard it.</LI>
|
||||
|
||||
<LI>
|
||||
Verify that the flip-flop status bit (CRTC Index 24, bit 7) is 0. If bit=1
|
||||
then feature is not supported, else continue to step 3.</LI>
|
||||
|
||||
<LI>
|
||||
Output an address value to the Attribute Address/Data register.</LI>
|
||||
|
||||
<LI>
|
||||
Verify that the flip-flop status bit (CRTC Index 24, bit 7) is 1. If bit=0
|
||||
then feature is not supported, else continue to step 5.</LI>
|
||||
|
||||
<LI>
|
||||
Input a value from the Input Status #1 Register (normally port 3DAh) and
|
||||
discard it.</LI>
|
||||
|
||||
<LI>
|
||||
Verify that the flip-flop status bit (CRTC Index 24, bit 7) is 0. If bit=1
|
||||
then feature is not supported, else feature is supported.</LI>
|
||||
</OL>
|
||||
<A NAME="color"></A><B>Accessing the Color Registers</B>
|
||||
<BR> The color registers require an altogether
|
||||
different technique; this is because the 256-color palette requires 3 bytes
|
||||
to store 18-bit color values. In addition the hardware supports the capability
|
||||
to load all or portions of the palette rapidly. To write to the palette,
|
||||
first you must output the value of the palette entry to the PEL Address
|
||||
Write Mode Register (port 3C8h.) Then you should output the component values
|
||||
to the PEL Data Register (port 3C9h), in the order red, green, then blue.
|
||||
The PEL Address Write Mode Register will then automatically increment,
|
||||
allowing the component values of the palette entry to be written to the
|
||||
PEL Data Register. Reading is performed similarly, except that the PEL
|
||||
Address Read Mode Register (port 3C7h) is used to specify the palette entry
|
||||
to be read, and the values are read from the PEL Data Register. Again,
|
||||
the PEL Address Read Mode Register auto-increments after each triplet is
|
||||
written. The current index for the current operation can be read from the
|
||||
PEL Address Write Mode Register. Reading port 3C7h gives the DAC State
|
||||
Register, which specifies whether a read operation or a write operation
|
||||
is in effect. As in the attribute registers, there is guaranteed way for
|
||||
an interrupt routine to access the color registers and return the color
|
||||
registers to the state they were in prior to access without some communication
|
||||
between the ISR and the main program. For some workarounds see the <A HREF="#attribute">Accessing
|
||||
the Attribute Registers</A> section above. To read the color registers:
|
||||
<OL>
|
||||
<LI>
|
||||
Read the DAC State Register and save the value for use in step 8.</LI>
|
||||
|
||||
<LI>
|
||||
Read the PEL Address Write Mode Register for use in step 8.</LI>
|
||||
|
||||
<LI>
|
||||
Output the value of the first color entry to be read to the PEL Address
|
||||
Read Mode Register.</LI>
|
||||
|
||||
<LI>
|
||||
Read the PEL Data Register to obtain the red component value.</LI>
|
||||
|
||||
<LI>
|
||||
Read the PEL Data Register to obtain the green component value.</LI>
|
||||
|
||||
<LI>
|
||||
Read the PEL Data Register to obtain the blue component value.</LI>
|
||||
|
||||
<LI>
|
||||
If more colors are to be read, repeat steps 4-6.</LI>
|
||||
|
||||
<LI>
|
||||
Based upon the DAC State from step 1, write the value saved in step 2 to
|
||||
either the PEL Address Write Mode Register or the PEL Address Read Mode
|
||||
Register.</LI>
|
||||
</OL>
|
||||
Note: Steps 1, 2, and 8 are hopelessly optimistic. This in no way guarantees
|
||||
that the state is preserved, and with some DAC implementations this may
|
||||
actually guarantee that the state is never preserved. See the <A HREF="vgadac.htm">DAC
|
||||
Operation</A> page for more details.
|
||||
|
||||
<P><A NAME="binary"></A><B>Binary Operations</B>
|
||||
<BR><B> </B>In order to better
|
||||
understand dealing with bit fields it is necessary to know a little bit
|
||||
about logical operations such as logical-and (AND), logical-or (OR), and
|
||||
exclusive-or(XOR.) These operations are performed on a bit by bit basis
|
||||
using the truth tables below. All of these operations are commutative,
|
||||
i.e. A OR B = B OR A, so you look up one bit in the left column and the
|
||||
other in the top row and consult the intersecting row and column for the
|
||||
answer.
|
||||
<BR>
|
||||
<CENTER><TABLE BORDER WIDTH="500" >
|
||||
<TR ALIGN=CENTER>
|
||||
<TD COLSPAN="3"><B>AND</B></TD>
|
||||
|
||||
<TD></TD>
|
||||
|
||||
<TD COLSPAN="3"><B>OR</B></TD>
|
||||
|
||||
<TD></TD>
|
||||
|
||||
<TD COLSPAN="3"><B>XOR</B></TD>
|
||||
</TR>
|
||||
|
||||
<TR ALIGN=CENTER>
|
||||
<TD WIDTH="10%"></TD>
|
||||
|
||||
<TD WIDTH="10%"><B>0</B></TD>
|
||||
|
||||
<TD WIDTH="10%"><B>1</B></TD>
|
||||
|
||||
<TD></TD>
|
||||
|
||||
<TD WIDTH="10%"></TD>
|
||||
|
||||
<TD WIDTH="10%"><B>0</B></TD>
|
||||
|
||||
<TD WIDTH="10%"><B>1</B></TD>
|
||||
|
||||
<TD></TD>
|
||||
|
||||
<TD WIDTH="10%"></TD>
|
||||
|
||||
<TD WIDTH="10%"><B>0</B></TD>
|
||||
|
||||
<TD WIDTH="10%"><B>1</B></TD>
|
||||
</TR>
|
||||
|
||||
<TR ALIGN=CENTER>
|
||||
<TD><B>0</B></TD>
|
||||
|
||||
<TD>0</TD>
|
||||
|
||||
<TD>0</TD>
|
||||
|
||||
<TD></TD>
|
||||
|
||||
<TD><B>0</B></TD>
|
||||
|
||||
<TD>0</TD>
|
||||
|
||||
<TD>1</TD>
|
||||
|
||||
<TD></TD>
|
||||
|
||||
<TD><B>0</B></TD>
|
||||
|
||||
<TD>0</TD>
|
||||
|
||||
<TD>1</TD>
|
||||
</TR>
|
||||
|
||||
<TR ALIGN=CENTER>
|
||||
<TD><B>1</B></TD>
|
||||
|
||||
<TD>0</TD>
|
||||
|
||||
<TD>1</TD>
|
||||
|
||||
<TD></TD>
|
||||
|
||||
<TD><B>1</B></TD>
|
||||
|
||||
<TD>1</TD>
|
||||
|
||||
<TD>1</TD>
|
||||
|
||||
<TD></TD>
|
||||
|
||||
<TD><B>1</B></TD>
|
||||
|
||||
<TD>1</TD>
|
||||
|
||||
<TD>0</TD>
|
||||
</TR>
|
||||
</TABLE></CENTER>
|
||||
<BR>
|
||||
<A NAME="example"></A><B>Example Register</B>
|
||||
<BR> The following table is an
|
||||
example of one particular register, the Mode Register of the Graphics Register.
|
||||
Each number from 7-0 represents the bit position in the byte. Many registers
|
||||
contain more than one field, each of which performs a different function.
|
||||
This particular chart contains four fields, two of which are two bits in
|
||||
length. It also contains two bits which are not implemented (to the best
|
||||
of my knowledge) by the standard VGA hardware.
|
||||
<BR>
|
||||
<TABLE BORDER WIDTH="600" CELLPADING="2" >
|
||||
<CAPTION ALIGN=TOP><B>Mode Register (Index 05h)</B></CAPTION>
|
||||
|
||||
<TR ALIGN=CENTER VALIGN=CENTER>
|
||||
<TD WIDTH="75">7</TD>
|
||||
|
||||
<TD WIDTH="75">6</TD>
|
||||
|
||||
<TD WIDTH="75">5</TD>
|
||||
|
||||
<TD WIDTH="75">4</TD>
|
||||
|
||||
<TD WIDTH="75">3</TD>
|
||||
|
||||
<TD WIDTH="75">2</TD>
|
||||
|
||||
<TD WIDTH="75">1</TD>
|
||||
|
||||
<TD WIDTH="75">0</TD>
|
||||
</TR>
|
||||
|
||||
<TR ALIGN=CENTER VALIGN=CENTER>
|
||||
<TD WIDTH="75"></TD>
|
||||
|
||||
<TD COLSPAN="2" WIDTH="150">Shift Register</TD>
|
||||
|
||||
<TD WIDTH="75">Odd/Even</TD>
|
||||
|
||||
<TD WIDTH="75">RM</TD>
|
||||
|
||||
<TD WIDTH="75"></TD>
|
||||
|
||||
<TD COLSPAN="2" WIDTH="150">Write Mode</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
<BR>
|
||||
<A NAME="bitfields"></A><B>Masking Bit-Fields</B>
|
||||
<BR> Your development environment
|
||||
may provide some assistance in dealing with bit fields. Consult your documentation
|
||||
for this. In addition it can be performed using the logical operators AND,
|
||||
OR, and XOR (for details on these operators see the <A HREF="#binary">Binary
|
||||
Operations</A> section above.) To change the value of the Shift Register
|
||||
field of the example register above, we would first mask out the bits we
|
||||
do not wish to change. This is accomplished by performing a logical AND
|
||||
of the value read from the register and a binary value in which all of
|
||||
the bits we wish to leave alone are set to 1, which would be 10011111b
|
||||
for our example. This leaves all of the bits except the Shift Register
|
||||
field alone and set the Shift Register field to zero. If this was our goal,
|
||||
then we would stop here and write the value back to the register. We then
|
||||
OR the value with a binary number in which the bits are shifted into position.
|
||||
To set this field to 10b we would OR the result of the AND with 01000000b.
|
||||
The resulting byte would then be written to the register. To set a bitfield
|
||||
to all ones the AND step is not necessary, similar to setting the bitfield
|
||||
to all zeros using AND. To toggle a bitfield you can XOR a value with a
|
||||
byte with a ones in the positions to toggle. For example XORing the value
|
||||
read with 01100000b would toggle the value of the Shift Register bitfield.
|
||||
By using these techniques you can assure that you do not cause any unwanted
|
||||
"side-effects" when modifying registers.
|
||||
|
||||
<P>Notice: All trademarks used or referred to on this page are the property
|
||||
of their respective owners.
|
||||
<BR>All pages are Copyright © 1997, 1998, J. D. Neal, except where
|
||||
noted. Permission for utilization and distribution is subject to the terms
|
||||
of the <A HREF="license.htm">FreeVGA Project Copyright License</A>.
|
||||
<BR>
|
||||
<BR>
|
||||
</BODY>
|
||||
</HTML>
|
||||
Reference in New Issue
Block a user