add directory study

This commit is contained in:
gohigh
2024-02-19 00:25:23 -05:00
parent b1306b38b1
commit f3774e2f8c
4001 changed files with 2285787 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,290 @@
PIC.TXT - 1.0
An allmost-all in one reference for the 8259 PIC found on most PCs.
Copyright 1994 Coridon Henshaw. All rights reserved.
May be distributed and used for nonprofit perposes only.
Coridon Henshaw @ 1:250/820 (Fidonet) or
Coridon.Henshaw@f820.n250.z1.fidonet.org (Internet)
Reference: 'The Undocumented PC' by Frank Van Gilluwe
All ports are one byte wide.
Port Direction Name Platform
------------------------------------------------------------------------------
20h Input 8259-1 Read Interrupt Request/Service registers All
General read data port for 20h commands 0Ah and 0Bh. See discription for
the above commands (below) for more details.
Port Direction Name Platform
------------------------------------------------------------------------------
20h Output 8259-1 Command Register All
This port provides general control for the first 8259 PIC. A command
listing is provided below. This port is most commonly used for sending the
EOI signal at the end of an interrupt.
There are more commands on the PIC, but I have only listed the commands that
actually do something, and are supported on PCs.
Command Name
--------------------------------------------------------------------------
2^4 Initialization mode
'2^4' isn't exactly a command, but it is a required bit that must be set
to enter initalisation mode.
Command byte bitmap:
Bit Name
---------------------------------
7 Unused
6 Unused
5 Unused
4 Initialization mode.
3 Clear: Edge triggered IRQ (PC,XT,AT)
Set: Level triggered IRQ (MCA)
Unused on EISA (Controlled from port 4D0h)
2 Unused
1 Clear: Cascade mode (AT+)
Set: Single mode (PC/XT)
0 Require 4th initalisation byte. MUST be set to one.
Command Name
--------------------------------------------------------------------------
0Ah Read Interrupt Request Register
Loads the interrupt request register into port 20h.
This command can be useful to see if any lower-priority hardware
interrupts are pending while a high-priority interrupt handler is
executing.
Result byte bitmap:
Bit Name
---------------------------------
7 IRQ 7 requests service
6 IRQ 6 requests service
5 IRQ 5 requests service
4 IRQ 4 requests service
3 IRQ 3 requests service
2 IRQ 2 requests service
1 IRQ 1 requests service
0 IRQ 0 requests service
Command Name
--------------------------------------------------------------------------
0Bh Read Interrupt In-Service Register
Loads the interrupt in-service register into port 20h
This command is generally used to distingish exception- and CPU-generated
interrupts when in V86 mode.
In-service bits for hardware interrupts are cleared by sending an EOI to
the PIC.
Result byte bitmap:
Bit Name
---------------------------------
7 IRQ 7 in-service
6 IRQ 6 in-service
5 IRQ 5 in-service
4 IRQ 4 in-service
3 IRQ 3 in-service
2 IRQ 2 in-service
1 IRQ 1 in-service
0 IRQ 0 in-service
Command Name
--------------------------------------------------------------------------
20h End of interrupt (EOI)
Tells the PIC that the end of an interrupt hander has been reached and to
alow all IRQs to trigger interrupts. Lower-priority IRQs are normally
locked out when a high-priority interrupt handler is executing.
Command Name
--------------------------------------------------------------------------
48h Clear special mask mode
Resets the PIC from special mask mode. See command 68h.
Command Name
--------------------------------------------------------------------------
6xh Specific EOI
Like command 20h, except clears in-service bits for a specific interrupt.
Commands 60h-67h refer to IRQs 0-7, respectivly.
No practial use as far as I can see.
Command Name
--------------------------------------------------------------------------
A0h Rotating priority EOI
Rotates the priority of IRQs opon interrupt compleation. For example:
IRQ 0 handler sends EOI to PIC, which then makes IRQ 0 the lowest priority
and moves the last-executed IRQ to the highest priority.
For this command to work corectly, ALL IRQ handlers must terminate using
this command instead of 20h or 60-67h.
No practical use unless you want to rewrite all hardware IRQ handlers.
Command Name
--------------------------------------------------------------------------
C0-C7h Select IRQ priority
Modifys the priority order of incoming IRQs.
Priority
Command Highest Lowest
----------------------------------
C0h 0 7 6 5 4 3 2 1
C1h 1 0 7 6 5 4 3 2
C2h 2 1 0 7 6 5 4 3
C3h 3 2 1 0 7 6 5 4
C4h 4 3 2 1 0 7 6 5
C5h 5 4 3 2 1 0 7 6
C6h 6 5 4 3 2 1 0 7
C7h 7 6 5 4 3 2 1 0
C7h is the default in PCs.
This command may be usful for high-speed serial I/O.
Command Name
--------------------------------------------------------------------------
68h Set special mask mode
This command can be used by a high-priority interrupt handler to alow
lower priority interrupts to be serviced while it is running. When
enabled, all interrupts that are enabled in the interrupt enable register
will be serviced, regardless of priority.
Command Name
--------------------------------------------------------------------------
E0-E7h EOI and select lowest priority.
Duplicate of command Cxh. The only difference is that this command also
sends an EOI. It is intended for use in environments where all IRQs are
equally important.
Port Direction Name Platform
------------------------------------------------------------------------------
21h Input 8259-1 Interrupt Enable Register All
Reads which interrupts are enabled.
Result byte bitmap:
Bit Name
---------------------------------
7 IRQ 7 disabled
6 IRQ 6 disabled
5 IRQ 5 disabled
4 IRQ 4 disabled
3 IRQ 3 disabled
2 IRQ 2 disabled
1 IRQ 1 disabled
0 IRQ 0 disabled
As you can see, if a bit is SET, the IRQ is disabled.
Port Direction Name Platform
------------------------------------------------------------------------------
21h Output 8259-1 Interrupt Enable & Initialization register All
Normal operating mode:
Command byte bitmap:
Bit Name
---------------------------------
7 IRQ 7 disabled
6 IRQ 6 disabled
5 IRQ 5 disabled
4 IRQ 4 disabled
3 IRQ 3 disabled
2 IRQ 2 disabled
1 IRQ 1 disabled
0 IRQ 0 disabled
Initialization mode:
Command byte 1: This command is sent to port 20h. See port 20h, command 2^4
Command byte 2:
Command byte bitmap:
Bit Name
---------------------------------
7 3
6 3
5 3 Select base interrupt for IRQs. Default 1 (IRQ 0 = 8)
4 3
3 3
2 Unused
1 Unused
0 Unused
Command byte 3:
Command byte bitmap:
Bit Name
---------------------------------
7 Slave attached to IRQ 7
6 Slave attached to IRQ 6
5 Slave attached to IRQ 5
4 Slave attached to IRQ 4
3 Slave attached to IRQ 3
2 Slave attached to IRQ 2 (AT+)
1 Slave attached to IRQ 1
0 Slave attached to IRQ 0
Bit 2 MUST be the only bit set on an AT+. On an XT, this byte MUST be 0.
Command byte 4:
Command byte bitmap:
Bit Name
---------------------------------
7 Unused
6 Unused
5 Unused
4 Unknown
3 3Buffered mode
2 3 00 = non-buffered (AT). 10 = buffered mode (PC/XT)
1 Not supported on PC. Set to 0
0 Must be set to 1
Port Direction Name Platform
------------------------------------------------------------------------------
70h Output CMOS/NMI AT+
Interrupt related. See a CMOS/RTC reference for data.
Port Direction Name Platform
------------------------------------------------------------------------------
A0h I/O PIC 2 Register AT+
Usage identical to port 20h. All references to IRQs 0-7 in the discription
refer to IRQs 8-15.
Port Direction Name Platform
------------------------------------------------------------------------------
A1h I/O PIC 2 Register AT+
Usage identical to port 21h. All references to IRQs 0-7 in the discription
refer to IRQs 8-15.

View File

@@ -0,0 +1,197 @@
8259A Interrupt Controller on the PC
====================================
GMCH 1 Dec 93 10:49
1. Introduction & Scope
The 8259A is a wonderful device. It has a number of modes of
operation, and some magic priority rotation features. On the
PC, however, only the very simplest mode is used.
This discussion is limited to the way in which the 8259A is
used on the PC.
The function of the 8259A is to take up to eight interrupt
sources, and feed any active interrupts, one at a time, to the
CPU. The processor can individually mask off interrupt
sources. The 8259A has a priority mechanism, so that lower
priority interrupt sources do not interrupt the CPU while it is
servicing a higher priority interrupt -- but a higher priority
interrupt will be passed on to the processor.
2. The Jargon
An interrupt source, fed into an 8259A, is known as an IRQ --
Interrupt Request. An 8259A has 8 IRQ inputs.
The 8 IRQ inputs are fed into an 8 bit Interrupt Request
Register (IRR), via some "rising edge" detection logic (8 bit
Edge Sense Register -- ESR).
The 8259A can be told to mask off any of the IRQ. The 8259A
has an 8 bit Interrupt Mask Register (IMR). A one bit in the
IMR masks off the corresponding IRQ.
To perform its priority arbitration the 8259A has an 8 bit In
Service Register (ISR). In the register a bit is set to 1 when
the corresponding interrupt has been passed to the CPU, and the
CPU has not yet signalled End of Interrupt (EOI)
The CPU interrupt input is known as INTR. The 8259A interrupt
output is known as INT, which is connected to the CPU (wait for
it) INTR, or to another 8259A's IRQ.
The 8 IRR bits are ANDed with the NOT of the IMR, giving the
interrupt request input to the priority arbitration logic.
Reading between the lines, there is an INT latch, which is set
by the OR of the bits of (IRR AND NOT IMR) higher than the
highest priority bit in the ISR.
On an original PC there are 8 possible interrupt sources IRQ0
to IRQ7, fed into one 8259A (I/O address #020..#03F).
On AT's and beyond, there are 16 possible interrupt sources
IRQ0 to IRQ15, fed into two 8259A's. One 8259A (known as #1,
I/O address #020..#03F) is the "Master" and the other is a
"Slave" (known as #2, I/O address #0A0..#0BF). Only the
Master's INT is connected to the CPU's INTR. The Slave's INT
is connected to the Master's IRQ2.
3. The Mechanisms
The PC sets the 8259A into:
* Edge Triggered Interrupts
* Cascaded (on AT and later) ; Single (on earlier machines)
* Not Special Fully Nested (to do with Slave 8259A, see below)
* Not Buffered Normal EOI (Not Automatic EOI on INTA)
With this in mind, we will start with the simple cases, and
work up.
3.1 One 8259A, All IRQ Unmasked, No Interrupts In Service
and None Active.
So we start from the simplest possible quiescent state. The
sequence of actions is as follows:
0 The ESR, ISR, IRR and IMR are all zero.
1 IRQ3 becomes active (goes to 1)
2 B3 of the ESR is set to 1
3 B3 of the IRR is set to 1
4 B3 of the IMR is 0, so the IRR B3 is passed to the
priority arbitration logic.
5 All bits of the ISR are 0 (no interrupts are in
service), so the priority arbitration logic sets the
INT latch -- so the INT output is set active.
6 Eventually the CPU issues the first of two INTA
cycles. The contents of the IRR are frozen. The
8279A selects the highest priority IRR (B3) and sets
the corresponding ISR (B3).
7 Setting B3 of the ISR clears B3 of the ESR.
8 The CPU issues the second of two INTA cycles. The
8279A issues the interrupt vector associated with the
highest priority ISR (B3). The contents of the IRR
are unfrozen.
9 The INT latch is cleared -- so the INT output is set
inactive.
10 B3 of the IRR is set to 0 (IRR is unfrozen and B3 of
ESR is zero).
11 At some time in the future, the CPU issues an EOI
command, which clears B3 of the ISR.
IRQ3 can remain active beyond step 10, without generating any
further interrupts -- because B3 of IRR has been cleared. To
produce another interrupt requires IRQ3 to go inactive (0), and
then active (1) again.
3.2 Meaning of "Edge Triggered Interrupt Mode"
The behaviour of the ESR, IRR and ISR described above is what
happens in the famous Edge Triggered Interrupt Mode.
The purpose is to allow for IRQ signals to be short down/up
pulses. When the 8259A is reset the ESR is set to zero. An
upward transition of the IRQ sets the corresponding ESR bit to
1, which allows the IRQ state to be copied to the IRR --
provoking the interrupt. When the interrupt is acknowledged
the ISR bit is set, which resets the ESR bit, which forces the
IRR bit to zero -- irrespective of the IRQ. So even if IRQ is
still 1 when the ISR bit is cleared, at End of Interrupt, no
further interrupts will be generated.
3.3 What Happens if IRQ Changes with the Interrupt is In Service
It is clear what happens if IRQ does not do any further down/up
transitions until after EOI. It is OK for IRQ to go down
before EOI, but going up again is not explicitly described in
the manuals.
If a down/up IRQ transition cannot be prevented before EOI,
then it can be (reasonably safely) assumed that this will
generate a further interrupt after EOI -- provided the IRQ is
still up (active, 1) at EOI. Multiple down/up transitions can
be assumed to have the same effect.
What happens if there are one or more down/up IRQ transitions
followed by a final down transition before EOI, is also
undocumented. I guess that this has no effect. The
corresponding IRR bit will follow the IRQ, but this may be
expected to have no effect on the (supposed) INT latch, because
the ISR bit prevents it.
Obviously, it would be safer to ensure that IRQ does not go
down and then up again before EOI (just down is OK). If this
is not possible, then I believe the given assumptions to be
reasonable -- perhaps MEJ's boys could help us !
3.4 Master and Slave Handling
The PC does not use "Special Fully Nested Mode". What this
means is that once one of the Slave's interrupts is In Service
it takes precedence over all other Slave interrupts.
Slave interrupts are generally indistinguishable from Master
interrupts. The only tricky bit is that an EOI must be sent to
both Master and Slave. EOI should be sent to the Slave first
-- to allow any lower priority interrupts on the slave to
assert themselves. The EOI to the Master then allows any lower
or equal priority interrupts to assert themselves.
3.5 Fiddling with Interrupt Masking
Clearing masking or unmasking an interrupt when all is quiet
(no IRQ, IRR or ISR) is trivially OK, and produces no side
effects.
If an interrupt was masked and is unmasked, then any "pending"
IRQ will immediately take effect.
According to the 8259A diagram published by Intel, the IMR mask
gates the IRR bits into the interrupt priority resolution
logic. Masking and unmasking an interrupt while its IRR is
active is equivalent, as far as the interrupt priority
resolution logic is concerned, to the IRQ coming and going.
The effect can be seen to be the same as in 3.3 above.
There is doubt and uncertainty about what happens if IRQ
bounces up and down while ISR is set. To avoid difficulties it
would be reasonable (unless the Intel diagram I am working from
is entirely wrong) to mask off the interrupt until EOI, and
then unmask it again -- assuming that there is some other way
of detecting and dealing with the reasons for IRQ changing.
----------end of document-----------
[Richard:]
Note the worry about IRQ boucing up and down... this all comes
back to the amusing behaviour of the serial chip whilst it is
being serviced.
Also, I have seen discussion (in the Crynwr packet driver source; on
a Simtel20 mirror near you) of broken Chips & Technology (?) 8259s which
do not handle non-specific EOIs properly.

Binary file not shown.

View File

@@ -0,0 +1,181 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<title>Using the CMOS Timer</title>
<body bgcolor="#FFFFFF" text="#000000">
<p align="center"><font size="6"><b>Using the 1024Hz CMOS Timer Interrupt</b></font></p>
<p align="center"><b>Copyright &copy; 1997 by </b><a
href="mailto:pcgpe@geocities.com"><b>Mark
Feldman</b></a></p>
<hr>
<p><font color="#FF0000"><b>Important Note:</b> I've recently
received a few e-mails from people claiming that the information
in this file doesn't work for them. I'm still trying to track
down the exact cause of the problems, until I&nbsp;do the
information in this file should be considered incomplete. It will
<i>not</i> work on all machines.</font></p>
<p><b>Introduction</b></p>
<p>I&nbsp;was originally going to write an article for the
ill-fated PCGPE&nbsp;2 on using the CMOS timer interrupt, but
since I no longer support MS-DOS that article will not be
written. Instead, I've written this short HTML page showing how
to enable and use it for anyone who's interested.</p>
<p><b>What is It?</b></p>
<p>The CMOS timer is a chip on the motherboard responsible for
keeping track of the time. It's connected to the motherboard
battery, which is why your computer keeps the correct time even
when you unplug it from the wall (unless of course you disconnect
the battery). When the computer is first booted the appropriate
BIOS function reads the time from the CMOS chip and sets the
appropriate variables in system memory. BIOS&nbsp;also
initializes the PIT&nbsp;chip, which then periodically (ie 18.2
times a second)&nbsp;generates an interrupt to update the time
variables. Messing with the PIT&nbsp;chip's frequency will affect
the computer's time, but it will automatically be set again the
next time the user boots and BIOS reads the CMOS chip again. You
can also read the time from the CMOS chip directly if you want to
set the correct time afterwards (details on how to do this are in
<a
href="ftp://x2ftp.oulu.fi/pub/msdos/programming/docs/helppc21.zip">HelpPC</a>).</p>
<p>One very useful feature of the CMOS timer is that it's capable
of generating regular interrupts. The frequency is fixed at
1024Hz, but that's is more than enough for most applications, and
in my experiments it has a negligable affect on overall system
performance. Best of all, it leaves the PIT chip free to do other
stuff. In fact, if you develop your application carefully you
probably won't have to use the PIT chip at all (unless you need
an extremely high resolution timer such as that required for
CPU&nbsp;instruction timing)! I've found the CMOS timer to be
safer, easier to implement and more reliable that PIT
(particularly when running under a Win95 DOS&nbsp;shell).</p>
<p><b>Ok, So How Do I&nbsp;Enable It?</b></p>
<p>The following code will enable the interrupts:</p>
<p><font color="#008080"><b><tt>outp(0x70, 0x0B); </tt></b></font></p>
<p><font color="#008080"><b><tt>outp(0x71, inp(0x71) | 0x40);</tt></b></font></p>
<p>The following code will disable the interrupts:</p>
<p><font color="#008080"><b><tt>outp(0x70, 0x0B); </tt></b></font></p>
<p><font color="#008080"><b><tt>outp(0x71, inp(0x71) &amp;0xBF);</tt></b></font></p>
<p>When the timer is enabled it will trigger interrupt 0x70 at a
rate of 1024Hz. Place a custom interrupt handler there to trap
it. As always, make sure you install your custom handler before
enabling the interrupt, get the old handler beforehand and be
sure to call it in your own, and make sure you clean up after
yourself when you're done.</p>
<p>(Details on what the above registers actually do can be found
in <a
href="ftp://x2ftp.oulu.fi/pub/msdos/programming/docs/helppc21.zip">HelpPC</a>).</p>
<p><b>How Would I&nbsp;Use This in a Real Application?</b></p>
<p>I'm sure there are many ways to use this, here is one I would
suggest. I'll assume that your application is doing real-time
sound mixing and needs accurate timing for animation control.</p>
<p>First of all I'd declare the following two variables:</p>
<pre><font color="#008080"><b><tt>volatile unsigned LONG&nbsp;time; // indicates elapsed time</tt></b></font></pre>
<pre><font color="#008080"><b><tt>int mixing=0; // a boolean variable</tt></b></font></pre>
<p>The <b>volatile</b> keyword is used to indicate to the
compiler that it should <i>always</i> read the contents of the
variable before using it (Most compilers often cache variables in
registers in order to improve performance. This is unacceptable
since the interrupt handler may change the value of the variable
at any time).</p>
<p>Next I&nbsp;would write an interrupt handler which looked
something like this:</p>
<pre><font color="#008080"><b>void interrupt handler(...) </b></font></pre>
<pre><font color="#008080"><b>{</b></font></pre>
<pre><font color="#008080"><b> time++; // Update time value</b></font></pre>
<pre><font color="#008080"><b> oldhandler(); // call the old handler</b></font></pre>
<pre><font color="#008080"><b> // If we are not already mixing then we should try and mix some more</b></font></pre>
<pre><font color="#008080"><b> if (!mixing)</b></font></pre>
<pre><font color="#008080"><b> {</b></font></pre>
<pre><font color="#008080"><b> mixing = 1; // Indicate that we are now mixing</b></font></pre>
<pre><font color="#008080"><b> _asm sti; // Enable interrupts</b></font></pre>
<pre><font color="#008080"><b> MixSound(); // Go mix</b></font></pre>
<pre><font color="#008080"><b> mixing = 0; // No longer mixing</b></font></pre>
<pre><font color="#008080"><b> }</b></font></pre>
<pre><font color="#008080"><b>}</b></font></pre>
<p>This code starts by incrementing the <b>time</b> variable.
Thus, the main application can simply read this variable at any
time and divide by 1024 to get the number of seconds elapsed (or
divide by 1.024 to get he number of milliseconds). The handler
should then calls the old handler so that the interrupt is
acknowledged (can also be accomplished with the
outp(0x20,0x20)&nbsp;instruction). The handler then does sound
mixing.</p>
<p>You can see that the interrupt handler I've designed calls a
function called MixSound(). I assume that this function
determines whether there is any sound to be mixed into the
playback buffer, and if so does whatever it needs to do. Now if
this function mixes 1/2 a second of sound (say)&nbsp;at a time,
then quite a few int 0x70's may be triggered while it is mixing.
Normally our handler wouldn't be called for these, and we'd see a
slight &quot;glitch&quot;&nbsp;in animation&quot; since the <b>time</b>
variable would have skipped over a few interrupts.. For this
purpose it is important that interrupts are enabled while the
mixing is in progress, so that further int 0x70's interrupt the
sound mixing function and are still processed (hence the <b>_asm
sti</b> statement). The problem though is that this will cause
the MixSound()&nbsp;function to be called again when it's already
in the middle of mixing sound - the result of which is
potentially disastrous. For this reason I&nbsp;maintain the <b>mixing</b>
variable, so that while mixing is in progress the variable is set
and the handler doesn't try to call the mixing routine again.</p>
<p>I&nbsp;haven't actually tried using the CMOS timer for tripple
buffering, but I&nbsp;imagine it would be pretty straightforward.
If you are in a 60Hz display mode then retraces would occurr
every 1024/60=17.0666 ticks or so (make sure you stay synched to
the retrace, I'd probably start polling the VGA card after about
15 ticks or so just to be safe). I'd use a loop similar to the
MixSound() loop above, which would instead poll the vertical
retrace bit waiting for the retrace to start. It should then read
the current time value, calculate when the next retrace is due to
start (current time +&nbsp;15)&nbsp;and then go do whatever it
has to do.</p>
<p>One last thing I'd like to point out is that the <b>time</b>
variable I&nbsp;use is a 32-bit unsigned long. This means that
it's value would wrap around to 0 after 4294967296 ticks, which
is about once every seven weeks. I&nbsp;can't imagine very many
programs which would be left running for this long, but it's best
to keep it in mind anyway.</p>
</body>
</html>

Binary file not shown.

View File

@@ -0,0 +1,158 @@
Port Description
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
000H-01fH DMA (Direct Memory Access) controller See DMA Ports
020H-03fH Interrupt Controller
==============================================================================
DMA (Direct Memory Access) is used for high-speed data transfers between I/O
devices and memory without intervention of the CPU. It is typically employed
by diskette and hard disk drivers, but it could be used for streaming tape or
any other device as long as it does not interfere with the operation of other
standard devices.
The original PC supports four 8-bit DMA channels, across a 20-bit address
space, using an Intel 8237A DMA controller chip. The <20>AT<41> supports 7 DMA
channels by cascading a second 8237A DMA controller. The differences between
PC and AT DMA are covered at the end of this section.
Channel Usage in PC and XT
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
0 memory refresh (highest priority)
1 not used
2 diskette adapter
3 hard disk adapter (lowest priority)
Port Description
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
000H-007H DMA base address an offset registers
All are 16-bit registers: read/write the low byte, then the high byte
at the same I/O port. Base addresses are offsets from a DMA Page (see
below).
000H Write: DMA channel 0 base address (also sets current address)
Read: DMA channel 0 current address
001H Write: DMA channel 0 base address and word count
Read: DMA channel 0 current word count
002H Write: DMA channel 1 base address
Read: DMA channel 1 current address
003H Write: DMA channel 1 base address and word count
Read: DMA channel 1 current word count
004H Write: DMA channel 2 base address (diskette adapter)
Read: DMA channel 2 current address "
005H Write: DMA channel 2 base address and word count "
Read: DMA channel 2 current word count "
006H Write: DMA channel 3 base address (hard disk adapter)
Read: DMA channel 3 current address "
007H Write: DMA channel 3 base address and word count "
Read: DMA channel 3 current word count "
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
008H-00fH DMA control/status registers
008H Write: DMA command register
<20>7<EFBFBD>6<EFBFBD>5<EFBFBD>4<EFBFBD>3<EFBFBD>2<EFBFBD>1<EFBFBD>0<EFBFBD>
<20> <20> <20> <20> <20> <20> <20> <20> <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҽ bit
<20> <20> <20> <20> <20> <20> <20> <20><> 0: 1=enable memory-to-memory DMA (ch0<68>ch1)
<20> <20> <20> <20> <20> <20> <20><><EFBFBD><EFBFBD> 1: 1=enable Ch0 address hold
<20> <20> <20> <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 2: 1=disable controller
<20> <20> <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 3: 1=select compressed timing mode
<20> <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 4: 1=enable rotating priority
<20> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 5: 1=select extended write mode; 0=late write
<20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 6: 1=select DRQ sensing as active high; 0=low
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 7: 1=select DACK sensing as active high; 0=low
Read: DMA status register
<20>7<EFBFBD>6<EFBFBD>5<EFBFBD>4<EFBFBD>3<EFBFBD>2<EFBFBD>1<EFBFBD>0<EFBFBD>
<20> <20> <20> <20> <20> <20> <20> <20> <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ľ bit
<20><><EFBFBD><EFBFBD><EFBFBD>ͼ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0-3: channel 0-3 has reached terminal count
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 4-7: channel 0-3 has a request pending
009H Write: request register
<20>7<EFBFBD>6<EFBFBD>5<EFBFBD>4<EFBFBD>3<EFBFBD>2<EFBFBD>1<EFBFBD>0<EFBFBD>
<20> unused <20> <20> <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ľ bit
<20> <20><><EFBFBD><EFBFBD> 0-1: select channel (00=0; 01=1; 10=2; 11=3)
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 2: 1=set request bit for channel; 0=reset request
00aH Write: single mask bit register
<20>7<EFBFBD>6<EFBFBD>5<EFBFBD>4<EFBFBD>3<EFBFBD>2<EFBFBD>1<EFBFBD>0<EFBFBD>
<20> unused <20> <20> <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ľ bit
<20> <20><><EFBFBD><EFBFBD> 0-1: select channel (00=0; 01=1; 10=2; 11=3)
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 2: 1=set mask for channel; 0=clear mask (enable)
00bH Write: mode register
<20>7<EFBFBD>6<EFBFBD>5<EFBFBD>4<EFBFBD>3<EFBFBD>2<EFBFBD>1<EFBFBD>0<EFBFBD>
<20> <20> <20> <20> <20> <20> <20> <20> <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ľ bit
<20>˼ <20> <20> <20>˼ <20><><EFBFBD><EFBFBD> 0-1: select channel (00=0; 01=1; 10=2; 11=3)
<20> <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 2-3: transfer type (00=verify=Nop; 01=write; 10=read)
<20> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 4: 1=enable auto-initialization
<20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 5: 1=select address increment; 0=address decrement
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 6-7: 00=demand mode; 01=single; 10=block; 11=cascade
00cH Write: clear byte pointer flip-flop. Any write clears the flip-flop so
that the next write to any of the 16-bit registers is decoded as
the low byte. The next is the high byte, then next is low, etc.
00dH Write: master clear. Any OUT clears the ctrlr (must be re-initialized)
Read: temporary reg. Last byte in memory-to-memory xfer (not used)
00eH Write: Clear mask registers. Any OUT enables all 4 channels.
00fH Write: master clear. Clear or mask any or all of the channels.
<20>7<EFBFBD>6<EFBFBD>5<EFBFBD>4<EFBFBD>3<EFBFBD>2<EFBFBD>1<EFBFBD>0<EFBFBD>
<20> <20> <20> <20> <20> <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҽ bit
<20> <20> <20> <20><> 0: 1=mask channel 0; 0=enable
<20> <20> <20><><EFBFBD><EFBFBD> 1: 1=mask channel 1;
<20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 2: 1=mask channel 2;
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 3: 1=mask channel 3;
Read: temporary reg. Last byte in memory-to-memory xfer (not used)
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
081H-08fH DMA page registers.
To select a starting address for a DMA operation, do an OUT to the page
register (ports 81H-83H) for the selected channel then set the base
address (ports 00H-07H) for the channel. A page register is set with a
4-bit value that represents bits 16-19 of the 20-bit DMA address. Since
the current address is a 16-bit value, it is not possible to cross a 64K
boundary (eg, address 1000:0, 2000:0, etc.) with a DMA operation.
081H Channel 2 page register (diskette DMA)
082H Channel 3 page register (hard disk DMA)
083H Channel 1 page register
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
<EFBFBD><EFBFBD>AT<EFBFBD> DMA <20> The DMA system on the AT is basically upwardly-compatible with PC
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> and XT DMA. In addition to the four 8-bit channels of the PC, the
AT adds a second 8237A-5 DMA controller which supports channels 4-7.
Channel Usage in AT
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
0 spare Ŀ
1 SDLC (Synchronous Data Link Control) <20><> 8-bit DMA channels
2 diskette adapter <20> <20>
3 hard disk adapter <20><> <20>
4 (controller 2) cascade for controller 1 Ŀ <20>
5 spare <20><> 16-bit DMA channels <20>
6 spare <20> <20>
7 spare <20><> <20>
<20>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
081H-08fH DMA page registers. On the AT, all 8 bits of the Page registers are<72>
used. They become the high 8-bits of a 24-bit address space (with the <20>
low 16-bits being set in a channel's base/current address register). <20>
The page size is 128K (64K words) so DMA transfers must not cross a 128K<38>
boundary (eg, address 2000:0, 4000:0, 6000:0, etc.) <20>
<20>
081H Channel 2 page register (diskette DMA) (address bits 16-23) <20>
082H Channel 3 page register (hard disk DMA) (address bits 16-23) <20>
083H Channel 1 page register (address bits 16-23) <20>
087H Channel 0 page register (address bits 16-23) <20>
089H Channel 6 page register (address bits 17-23) <20>
08bH Channel 5 page register (address bits 17-23) <20>
08aH Channel 7 page register (address bits 17-23) <20>
0ceH Channel 7 current word count
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
0d0H-0dfH <20>AT<41> DMA control/status registers

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,312 @@
IDEAL
ModeL large
;+--------------------------------------------------------------------------+
;| IBM-PC(tm) compatible programmer's DMA library |
;+--------------------------------------------------------------------------+
;| This assembly code defines 3 functions that are intended for use |
;| by C programmers in code that requires access to the DMA system. |
;| |
;| The general sequence for using the DMA is: |
;| int channel=1; |
;| if (dma_reset(channel)) |
;| abort(); |
;| if (dma_setup(channel,(char far *)My_Buffer,sizeof(My_Buffer),1)) |
;| abort(); |
;| /* Insert "foreground" code here. */ |
;| while (dma_done(channel)!=-1) { |
;| if (dma_errno) |
;| abort(); |
;| } |
;+--------------------------------------------------------------------------+
;| PUBLIC FUNCTIONS |
;| int far dma_reset(int Channel) |
;| int far dma_setup(int Channel,char far *Buffer,unsigned Length,int Dir) |
;| int far dma_done(int Channel) |
;+--------------------------------------------------------------------------+
;| PUBLIC DATA |
;| int far dma_errno |
;| char far *dma_errlist[] |
;+--------------------------------------------------------------------------+
Status EQU 08h ;DMAC status port (read) \ same port
Command EQU 08h ;DMAC command port (write) / (read/write)
;STATUS/COMMAND BYTE: ("*" represents defaults)
; [ 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 ]
; Bit 0: Memory-to-memory transfer 0 => disable*
; 1 => enable
; 1: "Don't Care" if mem-to-mem disabled (Bit 0==0)*
; Channel 0 address hold 0 => disable
; 1 => enable
; 2: Controller enable 0 => enable*
; 1 => disable
; 3: "Don't Care" if mem-to-mem enabled (Bit 0==1)
; Timing 0 => Normal?
; 1 => Compressed?
; 4: Priority 0 => Fixed?
; 1 => Rotating
; 5: "Don't care" if compressed timing (Bit 3==1)
; Write selection 0 => Late
; 1 => Extended
; 6: DREQ sense active 0 => High
; 1 => Low
; 7: DACK sense active 0 => Low
; 1 => High
Request EQU 09h ;DMAC channel request (write-only)
;REQUEST BYTE:
; [ 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 ]
; \__________________/ | \_____/
; Don't care | |
; | +------+ 00 = Select channel 0
; | | 01 = Select channel 1
; | | 10 = Select channel 2
; | + 11 = Select channel 3
; +---+ 0 = Reset request bit
; + 1 = Set request bit
DMA_Mask EQU 0Ah ;DMAC DMA_Mask (write-only)
Mode EQU 0Bh ;DMAC mode (read/write)
byte_ptr EQU 00ch ; byte pointer flip-flop
addr EQU 000h ; per-channel base address
count EQU 001h ; per-channel byte count
read_cmd EQU 048h ; read mode
write_cmd EQU 044h ; write mode
set_cmd EQU 000h ; DMA_Mask set
reset_cmd EQU 004h ; DMA_Mask reset
; dma controller page register table
; this table maps from channel number to the i/o port number of the
; page register for that channel
DATASEG
page_table DW 00087h ; channel 0
DW 00083h ; channel 1
DW 00081h ; channel 2
DW 00082h ; channel 3
; "Extra" messages are for future compatability with the Virtual DMA
; specification.
DMA_E0 DB 0
DMA_E1 DB "Region not in contiguous memory.",0
DMA_E2 DB "Region crossed a physical alignment boundary.",0
DMA_E3 DB "Unable to lock pages.",0
DMA_E4 DB "No buffer available.",0
DMA_E5 DB "Region too large for buffer.",0
DMA_E6 DB "Buffer currently in use.",0
DMA_E7 DB "Invalid memory region.",0
DMA_E8 DB "Region was not locked.",0
DMA_E9 DB "Number of physical pages greater than table length.",0
DMA_EA DB "Ivalid buffer ID.",0
DMA_EB DB "Copy out of buffer range.",0
DMA_EC DB "Invalid DMA channel number.",0
_dma_errlist DD DMA_E0, DMA_E1, DMA_E2, DMA_E3, DMA_E4, DMA_E5, DMA_E6, DMA_E7, DMA_E8, DMA_E9, DMA_EA, DMA_EB, DMA_EC
_dma_errno DW 0
;char far *dma_errlist[]
;int _dma_errno
PUBLIC _dma_errlist,_dma_errno
CODESEG
MACRO zero reg
xor reg,reg
ENDM zero
PUBLIC _dma_setup,_dma_reset,_dma_done
;+---------------------------------------------------------------------------+
;| int far dma_setup(int Channel,char far *Buffer,unsigned Length,int Dir) |
;| ------------------------------------------------------------------------- |
;| Channel = 0-3 !Channel 0 is often reserved for memory refresh! |
;| Buffer = Address of data to transfer |
;| Length = Length of data to transfer |
;| Dir = Direction to move bytes. 1 == Out to the BUS (TO the card) |
;| 0 == In from the BUS and cards. |
;| ------------------------------------------------------------------------- |
;| Returns: 0 if no errors (dma_errno == 0) |
;| -1 if errors occured (dma_errno set to indicate error.) |
;+---------------------------------------------------------------------------+
PROC _dma_setup FAR
ARG Channel:WORD,Buffer:DWORD,Len:WORD,Dir:WORD
push bp
mov bp,sp
push bx cx dx si di
pushf
mov [_dma_errno],0
;Convert seg:ofs Buffer to 20-bit physical address
;Assumes operating in 8086/real-Mode
mov bx,[WORD PTR Buffer]
mov ax,[WORD PTR Buffer+2]
mov cl,4
rol ax,cl
mov ch,al
and al,0F0h
add ax,bx
adc ch,0
and ch,0Fh
mov di,ax
; (ch << 16) + di == The physical buffer base.
;Calculate the port to receive this address
mov bx,[Channel]
cmp bx,3
jbe @@OkChannel
mov [_dma_errno],0Ch
mov ax,-1
jmp @@ExitPt
@@OkChannel:
shl bx,1
;bx == Port # Channel*2
;Determine which command byte will be written later
cmp [WORD PTR Dir],0
jnz SHORT @@Do_Read
mov al,write_cmd
jmp SHORT @@Do_Mode
@@Do_Read:
mov al,read_cmd
@@Do_Mode:
push cx
mov cx,[Channel]
add al,cl
zero ah
mov si,ax
mov ax,set_cmd
add al,cl
pop cx
mov cl,al
;si contains READ/WRITE command for DMA controller
;cl contains confirmation command for DMA controller
;-------------------------------------------------------------------------
; Calculations have been done ahead of time to minimize time with
; interrupts disabled.
;
; ch:di == physical base address
;
; cl == Confirmation command (Tells DMA we're done bothering it.)
;
; bx == I/O port Channel*2 (This is where the address is written)
;
; si == Mode command for DMA
;-------------------------------------------------------------------------
mov ax,di ;Let's check the address to see if we
add ax,[Len] ;span a page boundary with our length
jnc @@BoundaryOk ;Do we?
mov [_dma_errno],2 ; y: Error #2
mov ax,-1 ; Return -1
jmp @@ExitPt ; See ya...
@@BoundaryOk: ; n: Continue with action
cli ;Disable interrupts while mucking with DMA
;The "byte pointer" is also known as the LSB/MSB flip flop.
;By writing any value to it, the DMA controller registers are prepared
;to accept the address and length values LSB first.
mov dx,byte_ptr ;Reset byte pointer Flip/flop
out dx,al ;All we have to do is write to it
mov ax,di ;ax=LSW of 20-bit address
mov dx,bx ;dx=DMAC Base Address port
out dx,al ;Store LSB
mov al,ah
out dx,al ;Store next byte
mov al,ch ;al=Page number
mov dx,[bx + OFFSET page_table] ;dx=Port is the "Page index"
out dx,al ;Store the page
;Write length to port Channel*2 + 1
mov ax,[Len]
mov dx,bx ;dx=DMAC Base Adress port
inc dx ;dx=DMAC Count port (1 after Base address)
out dx,al ;Write LSB of Length
mov al,ah
out dx,al ;Write MSB
mov ax,si ;Load pre-calculated mode
mov dx,Mode ;dx=DMAC mode register
out dx,al ;Write it to the DSP
mov dx,DMA_Mask ;dx=DMAX DMA_Mask register
mov al,cl ;al=pre-calulated DMA_Mask value
out dx,al ;Write DMA_Mask
mov ax,0 ;Return with no error
@@ExitPt: ;Restore stack and return
popf
pop di si dx cx bx
pop bp
ret
ENDP _dma_setup
;+---------------------------------------------------------------------------+
;| int far dma_reset(int Channel) |
;| ------------------------------------------------------------------------- |
;| Channel = 0-3 |
;| Resets the specified channel. |
;| ------------------------------------------------------------------------- |
;| Returns 0 if Ok, -1 and sets dma_errno on error |
;+---------------------------------------------------------------------------+
PROC _dma_reset FAR
ARG Channel:Word
push bp
mov bp,sp
push dx
mov [_dma_errno],0
cmp [Channel],3
jbe @@OkChannel
mov [_dma_errno],0Ch
mov ax,-1
jmp @@Exit_Pt
@@OkChannel:
mov dx,DMA_Mask
mov ax,reset_cmd
add ax,[Channel]
out dx,al
mov ax,0
@@Exit_Pt:
pop dx
pop bp
ret
ENDP _dma_reset
;+---------------------------------------------------------------------------+
;| int far dma_done(Channel) |
;| ------------------------------------------------------------------------- |
;| Channel = 0-4 |
;| ------------------------------------------------------------------------- |
;| Returns: -1 if DMA transaction completed |
;| (Maybe it returns the number of bytes left to transfer?) |
;| dma_errno == 0 if no error, otherwise equals error number |
;+---------------------------------------------------------------------------+
PROC _dma_done FAR
ARG Channel:Word
push bp
mov bp,sp
pushf
push dx
cmp [Channel],3
jbe @@OkChannel
mov ax,-1
mov [_dma_errno],0Ch
jmp @@Exit_Pt
@@OkChannel:
mov dx,[Channel]
shl dx,1
add dx,count
cli
in al,dx
mov ah,al
in al,dx
xchg al,ah
@@Exit_Pt:
pop dx
popf
pop bp
ret
ENDP _dma_done
END

View File

@@ -0,0 +1,382 @@
+---------------------[ Rage Technologies, Inc. ]-------------------------+
-----------------------------------------------------------------------------
How to program the DMA - by Night Stalker
-----------------------------------------------------------------------------
When you want to start a DMA transfer, you need to know three things:
- Where the memory is located (what page),
- The offset into the page, and
- How much you want to transfer.
Since the DMA can work in both directions (memory to I/O card, and I/O
card to memory), you can see how the Sound Blaster can record as well as
play by using DMA.
The DMA has two restrictions which you must abide by:
- You cannot transfer more than 64K of data in one shot, and
- You cannot cross a page boundary.
Restriction #1 is rather easy to get around. Simply transfer the first
block, and when the transfer is done, send the next block.
For those of you not familiar with pages, I'll try to explain.
Picture the first 1MB region of memory in your system. It is divided
into 16 pages of 64K a piece like so:
Page Segment:Offset address
---- ----------------------
0 0000:0000 - 0000:FFFF
1 1000:0000 - 1000:FFFF
2 2000:0000 - 2000:FFFF
. .000:0000 - .000:FFFF
Okay, remember the three things needed by the DMA? Look back if you
need to. We can stuff this data into a structure for easy accessing:
typedef struct
{
char page;
unsigned int offset;
unsigned int length;
} DMA_block;
Now, how do we find a memory pointer's page and offset? Easy. Use
the following code:
void LoadPageAndOffset(DMA_block *blk, char *data)
{
unsigned int temp, segment, offset;
unsigned long foo;
segment = FP_SEG(data);
offset = FP_OFF(data);
blk->page = (segment & 0xF000) >> 12;
temp = (segment & 0x0FFF) << 4;
foo = offset + temp;
if (foo > 0xFFFF)
blk->page++;
blk->offset = (unsigned int)foo;
}
Most (if not all) of you are probably thinking, "What the heck is he doing
there?" I'll explain.
The FP_SEG and FP_OFF macros find the segment and the offset of the data
block in memory. Since we only need the page (look back at the table above),
we can take the upper 4 bits of the segment to create our page.
The rest of the code takes the segment, adds the offset, and sees if the
page needs to be advanced or not. (Note that a memory region can be located at
2FFF:F000, and a single byte increase will cause the page to increase by one.)
In plain English, the page is the highest 4 bits of the absolute 20 bit
address of our memory location. The offset is the lower 12 bits of the
absolute 20 bit address plus our offset.
Now that we know where our data is, we need to find the length.
The DMA has a little quirk on length. The true length sent to the DMA
is actually length + 1. So if you send a zero length to the DMA, it actually
transfers one byte, whereas if you send 0xFFFF, it transfers 64K. I guess
they made it this way because it would be pretty senseless to program the
DMA to do nothing (a length of zero), and in doing it this way, it allowed a
full 64K span of data to be transferred.
Now that you know what to send to the DMA, how do you actually start it?
This enters us into the different DMA channels.
-----------------------------------------------------------------------------
The DMA has 4 different channels to send 8-bit data. These channels are
0, 1, 2, and 3, respectively. You can use any channel you want, but if you're
transferring to an I/O card, you need to use the same channel as the card.
(ie: Sound Blaster uses DMA channel 1 as a default.)
There are 3 ports that are used to set the DMA channel:
- The page register,
- The address (or offset) register, and
- The word count (or length) register.
The following chart will describe each channel and it's corresponding
port number:
DMA Channel Page Address Count
------------------------------------
0 87h 0h 1h
1 83h 2h 3h
2 81h 4h 5h
3 82h 6h 7h
4 8Fh C0h C2h
5 8Bh C4h C6h
6 89h C8h CAh
7 8Ah CCh CEh
(Note: Channels 4-7 are 16-bit DMA channels. See below for more info.)
Since you need to send a two-byte value to the DMA (the offset and the
length are both two bytes), the DMA requests you send the low byte of data
first, then the high byte. I'll give a thorough example of how this is done
momentarily.
The DMA has 3 registers for controlling it's state. Here is the bitmap
layout of how they are accessed:
Mask Register (0Ah):
--------------------
MSB LSB
x x x x x x x x
------------------- - -----
| | | 00 - Select channel 0 mask bit
| | +---- 01 - Select channel 1 mask bit
| | 10 - Select channel 2 mask bit
| | 11 - Select channel 3 mask bit
| |
| +---------- 0 - Clear mask bit
| 1 - Set mask bit
|
+----------------------- xx - Don't care
Mode Register (0Bh):
--------------------
MSB LSB
x x x x x x x x
----- - - ----- -----
| | | | | 00 - Channel 0 select
| | | | +---- 01 - Channel 1 select
| | | | 10 - Channel 2 select
| | | | 11 - Channel 3 select
| | | |
| | | | 00 - Verify transfer
| | | +------------ 01 - Write transfer
| | | 10 - Read transfer
| | |
| | +-------------------- 0 - Autoinitialized
| | 1 - Non-autoinitialized
| |
| +------------------------ 0 - Address increment select
|
| 00 - Demand mode
+------------------------------ 01 - Single mode
10 - Block mode
11 - Cascade mode
DMA clear selected channel (0Ch):
---------------------------------
Outputting a zero to this port stops all DMA processes that are currently
happening as selected by the mask register (0Ah).
--------------------------------------------------------------------------------
Some of the most common modes to program the mode register are:
- 45h: Write transfer (I/O card to memory), and
- 49h: Read transfer (memory to I/O card).
Both of these assume DMA channel 1 for all transfers.
Now, there's also the 16-bit DMA channels as well. These shove two bytes
of data at a time. That's how the Sound Blaster 16 works as well in 16-bit
mode.
Programming the DMA for 16-bits is just as easy as 8 bit transfers. The
only difference is you send data to different I/O ports. The 16-bit DMA also
uses 3 other control registers as well:
Mask Register (D4h):
--------------------
MSB LSB
x x x x x x x x
------------------- - -----
| | | 00 - Select channel 4 mask bit
| | +---- 01 - Select channel 5 mask bit
| | 10 - Select channel 6 mask bit
| | 11 - Select channel 7 mask bit
| |
| +---------- 0 - Clear mask bit
| 1 - Set mask bit
|
+----------------------- xx - Don't care
Mode Register (D6h):
--------------------
MSB LSB
x x x x x x x x
----- - - ----- -----
| | | | | 00 - Channel 4 select
| | | | +---- 01 - Channel 5 select
| | | | 10 - Channel 6 select
| | | | 11 - Channel 7 select
| | | |
| | | | 00 - Verify transfer
| | | +------------ 01 - Write transfer
| | | 10 - Read transfer
| | |
| | +-------------------- 0 - Autoinitialized
| | 1 - Non-autoinitialized
| |
| +------------------------ 0 - Address increment select
|
| 00 - Demand mode
+------------------------------ 01 - Single mode
10 - Block mode
11 - Cascade mode
DMA clear selected channel (D8h):
---------------------------------
Outputting a zero to this port stops all DMA processes that are currently
happening as selected by the mask register (D4h).
Now that you know all of this, how do you actually use it? Here is sample
code to program the DMA using our DMA_block structure we defined before.
--------------------------------------------------------------------------------
/* Just helps in making things look cleaner. :) */
typedef unsigned char uchar;
typedef unsigned int uint;
/* Defines for accessing the upper and lower byte of an integer. */
#define LOW_BYTE(x) (x & 0x00FF)
#define HI_BYTE(x) ((x & 0xFF00) >> 8)
/* Quick-access registers and ports for each DMA channel. */
uchar MaskReg[8] = { 0x0A, 0x0A, 0x0A, 0x0A, 0xD4, 0xD4, 0xD4, 0xD4 };
uchar ModeReg[8] = { 0x0B, 0x0B, 0x0B, 0x0B, 0xD6, 0xD6, 0xD6, 0xD6 };
uchar ClearReg[8] = { 0x0C, 0x0C, 0x0C, 0x0C, 0xD8, 0xD8, 0xD8, 0xD8 };
uchar PagePort[8] = { 0x87, 0x83, 0x81, 0x82, 0x8F, 0x8B, 0x89, 0x8A };
uchar AddrPort[8] = { 0x00, 0x02, 0x04, 0x06, 0xC0, 0xC4, 0xC8, 0xCC };
uchar CountPort[8] = { 0x01, 0x03, 0x05, 0x07, 0xC2, 0xC6, 0xCA, 0xCE };
void StartDMA(uchar DMA_channel, DMA_block *blk, uchar mode)
{
/* First, make sure our 'mode' is using the DMA channel specified. */
mode |= DMA_channel;
/* Don't let anyone else mess up what we're doing. */
disable();
/* Set up the DMA channel so we can use it. This tells the DMA */
/* that we're going to be using this channel. (It's masked) */
outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
/* Clear any data transfers that are currently executing. */
outportb(ClearReg[DMA_channel], 0x00);
/* Send the specified mode to the DMA. */
outportb(ModeReg[DMA_channel], mode);
/* Send the offset address. The first byte is the low base offset, the */
/* second byte is the high offset. */
outportb(AddrPort[DMA_channel], LOW_BYTE(blk->offset));
outportb(AddrPort[DMA_channel], HI_BYTE(blk->offset));
/* Send the physical page that the data lies on. */
outportb(PagePort[DMA_channel], blk->page);
/* Send the length of the data. Again, low byte first. */
outportb(CountPort[DMA_channel], LOW_BYTE(blk->length));
outportb(CountPort[DMA_channel], HI_BYTE(blk->length));
/* Ok, we're done. Enable the DMA channel (clear the mask). */
outportb(MaskReg[DMA_channel], DMA_channel);
/* Re-enable interrupts before we leave. */
enable();
}
void PauseDMA(uchar DMA_channel)
{
/* All we have to do is mask the DMA channel's bit on. */
outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
}
void UnpauseDMA(uchar DMA_channel)
{
/* Simply clear the mask, and the DMA continues where it left off. */
outportb(MaskReg[DMA_channel], DMA_channel);
}
void StopDMA(uchar DMA_channel)
{
/* We need to set the mask bit for this channel, and then clear the */
/* selected channel. Then we can clear the mask. */
outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
/* Send the clear command. */
outportb(ClearReg[DMA_channel], 0x00);
/* And clear the mask. */
outportb(MaskReg[DMA_channel], DMA_channel);
}
uint DMAComplete(uchar DMA_channel)
{
/* Register variables are compiled to use registers in C, not memory. */
register int z;
z = CountPort[DMA_channel];
outportb(0x0C, 0xFF);
/* This *MUST* be coded in Assembly! I've tried my hardest to get it */
/* into C, and I've had no success. :( (Well, at least under Borland.) */
redo:
asm {
mov dx,z
in al,dx
mov bl,al
in al,dx
mov bh,al
in al,dx
mov ah,al
in al,dx
xchg ah,al
sub bx,ax
cmp bx,40h
jg redo
cmp bx,0FFC0h
jl redo
}
return _AX;
}
--------------------------------------------------------------------------------
I think all the above functions are self explanatory except for the last
one. The last function returns the number of bytes that the DMA has
transferred to (or read from) the device. I really don't know how it works
as it's not my code. I found it laying on my drive, and I thought it might
be somewhat useful to those of you out there. You can find out when a DMA
transfer is complete this way if the I/O card doesn't raise an interrupt.
DMAComplete() will return -1 (or 0xFFFF) if there is no DMA in progress.
Don't forget to load the length into your DMA_block structure as well
before you call StartDMA(). (When I was writing these routines, I forgot
to do that myself... I was wondering why it was transferring garbage.. <G>)
I hope you all have caught on to how the DMA works by now. Basically it
keeps a list of DMA channels that are running or not. If you need to change
something in one of these channels, you mask the channel, and reprogram. When
you're done, you simply clear the mask, and the DMA starts up again.
If anyone has problems getting this to work, I'll be happy to help. Send
us mail at the address below, and either I or another Rage member will fix
your problem(s).
Enjoy!
- Night Stalker

View File

@@ -0,0 +1,506 @@
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
INTRO TO DMA by Draeden of VLA
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
DMA means Direct Memory Access. You probably already know where and
why you use it, so I'll skip right down to the dirty stuff. This all
should speak for it's self, so... Enjoy.
Draeden /VLA
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
To do a DMA transfer, you need to know a few things:
1) Address of the memory to access
2) Length of data to read/write
This can all be put into a structure:
STRUC DMAInfo
Page db ?
Offset dw ?
Length dw ?
ENDS
Page is the highest 4 bits of the absolute 20 bit address of the memory
location. Note that DMA transfers CANNOT cross 64k page boundries.
The Length is actually LENGTH-1; sending in a 0 will move 1 byte,
sending a 0FFFFh will move 64k.
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
; IN: DX:AX = segment/offset address of memory area
;
;OUT: DH = Page (0-F) (DL is destroyed)
; AX = Offset
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
PROC MakePage
push bx
mov bl,dh
shr bl,4 ;isolate upper 4 bits of segment
shl dx,4 ;make segment into ABS address
add ax,dx ;add the offset and put it in AX
adc bl,0 ;complete the addition
mov dh,bl ;put the PAGE where it goes
pop bx ; DH:AX is now the PAGE:OFFSET address
ret
ENDP
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Programming DMA channels 0 thru 3
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
There are 3 ports that are DMA channel specific:
1) The Page register
2) The DMA count (length) register
3) The memory address (offset register)
They are as follows:
DMACH PAGE ADDRESS LENGTH
0 87h 0 1
1 83h 2 3
2 81h 4 5
3 82h 6 7
And now some general registers:
DMA Mask Register: 0Ah
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
bit 7 - 3 = 0 Reserved
bit 2 = 0 clear mask
= 1 set mask
bits 1 - 0 = 00 Select channel 0
= 01 select channel 1
= 10 select channel 2
= 11 select channel 3
USE: You must set the mask of the channel before you
can reprogram it.
DMA Mode Register: 0Bh
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
bit 7 - 6 = 00 Demand mode
= 01 Signal mode
= 10 Block mode
= 11 Cascade mode
bit 5 - 4 = 0 Reserved
bit 3 - 2 = 00 Verify operation
= 01 Write operation
= 10 Read operation
= 11 Reserved
bits 1 - 0 = 00 Select channel 0
= 01 select channel 1
= 10 select channel 2
= 11 select channel 3
USE: Tell the DMAC what to do. Common modes are:
48h (Read operation, Signal mode)
Used to read data from host memory and send to whomever
polls it.
44h (Write operation, Signal mode)
Used to write data taken from a device to memory.
DMA clear byte ptr: 0Ch
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
USE: Send a zero to reset the internal ptrs
WHAT TO DO:
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
1) Set the Mask bit for the channel
mov al,4
add al,[DMA_Channel]
out 0ah,al
2) Clear Byte Ptr
sub al,al
out 0Ch,al
3) Set the DMA transfer mode
mov al,48h ;MODE output (read)
add al,[DMA_Channel]
out 0Bh,al
4) Set the memory ADDRESS and LENGTH
; AX = offset
; CX = Length
;[DMA_Base] = port # of memory address
mov dx,[DMA_Base]
out dx,al ;send lower byte address
mov al,ah
out dx,al ;send high byte address
inc dl ;point to Count port
mov al,cl
out dx,al ;send low byte length
mov al,ch
out dx,al ;send high byte length
5) Set the DMA page
; AL = Page
mov dx,[Dma_Page]
out dx,al ; write the Page
6) Clear DMA mask bit
mov al,[byte DMA_Channel]
out 0Ah,al ; port 0Ah, DMA-1 mask reg bit
7) Program the other device that is going to use the DMA output/input
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
; This routine programs the DMAC for channels 0-3
;
; IN: [DMA_Channel], [DMAbaseAdd], [DMApageReg] must be setup
; [DAMBaseAdd] = Memory Address port
;
; dh = mode
; ax = address
; cx = length
; dl = page
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
PROC Prog_DMA03 NEAR
push bx
mov bx,ax
mov al,4
add al,[DMA_Channel]
out 0Ah,al ; mask reg bit
sub al,al
out 0Ch,al ; clr byte ptr
mov al,dh
add al,[DMA_Channel]
out 0Bh,al ; set mode reg
push dx
mov dx,[DMAbaseAdd]
mov al,bl
out dx,al ; set base address low
mov al,bh
out dx,al ; set base address high
inc dx ;point to length
mov al,cl
out dx,al ; set length low
mov al,ch
out dx,al ; set length high
pop dx
mov al,dl
mov dx,[DmaPageReg]
out dx,al ; set DMA page reg
mov al,[DMA_Channel]
out 0Ah,al ; unmask (activate) dma channel
pop bx
ret
ENDP
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Programming DMA channels 4 thru 7
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Again, there are 3 ports that are DMA channel specific:
1) The Page register
2) The DMA count (length) register
3) The memory address (offset register
They are as follows:
DMACH PAGE ADDRESS LENGTH
4 8Fh C0h C2h
5 8Bh C4h C6h
6 89h C8h CAh
7 8Ah CCh CEh
And now some general registers:
DMA Mask Register: 0D4h
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
bit 7 - 3 = 0 Reserved
bit 2 = 0 clear mask
= 1 set mask
bits 1 - 0 = 00 Select channel 4
= 01 select channel 5
= 10 select channel 6
= 11 select channel 7
USE: You must set the mask of the channel before you
can reprogram it.
DMA Mode Register: 0D6h
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
bit 7 - 6 = 00 Demand mode
= 01 Signal mode
= 10 Block mode
= 11 Cascade mode
bit 5 - 4 = 0 Reserved
bit 3 - 2 = 00 Verify operation
= 01 Write operation
= 10 Read operation
= 11 Reserved
bits 1 - 0 = 00 Select channel 4
= 01 select channel 5
= 10 select channel 6
= 11 select channel 7
USE: Tell the DMAC what to do. Common modes are:
48h (Read operation, Signal mode)
Used to read data from host memory and send to whomever
polls it.
44h (Write operation, Signal mode)
Used to write data taken from a device to memory.
DMA clear byte ptr: 0D8h
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
USE: Send a zero to reset the internal ptrs
WHAT TO DO: (exactly the same thing, just different io PORTs)
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
1) Set the Mask bit for the channel
mov al,[DMA_Channel] ;because the DMA's are 4-7, bit #3
out 0D4h,al ; is already set
2) Clear Byte Ptr
sub al,al
out 0D8h,al
3) Set the DMA transfer mode
mov al,[DMA_Channel]
sub al,4
or al,48h ;MODE output (read)
out 0D6h,al
4) Set the memory ADDRESS and LENGTH
; AX = offset
; CX = Length
;[DMA_Base] = port # of memory address
mov dx,[DMA_Base]
out dx,al ;send lower byte address
mov al,ah
out dx,al ;send high byte address
add dl,2 ;point to Count port (seperated by 2)
mov al,cl
out dx,al ;send low byte length
mov al,ch
out dx,al ;send high byte length
5) Set the DMA page
; AL = Page
mov dx,[Dma_Page]
out dx,al ; write the Page
6) Clear DMA mask bit
mov al,[byte DMA_Channel]
and al,00000011b
out 0d4h,al ; port 0Ah, DMA-1 mask reg bit
7) Program the other device that is going to use the DMA output/input
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
; This routine programs the DMAC for channels 4-7
;
; IN: [DMA_Channel], [DMAbaseAdd], [DMApageReg] must be setup
; [DAMBaseAdd] = Memory Address port
;
; dh = mode
; ax = address
; cx = length
; dl = page
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
PROC Prog_DMA47 NEAR
push bx
mov bx,ax
mov al,[DMA_Channel]
out 0D4h,al ; mask reg bit
sub al,al
out 0D8h,al ; clr byte ptr
mov al,[DMA_Channel]
sub al,4
add al,dh
out 0D6h,al ; set mode reg
push dx
mov dx,[DMAbaseAdd]
mov al,bl
out dx,al ; set base address low
mov al,bh
out dx,al ; set base address high
add dl,2 ;point to length
mov al,cl
out dx,al ; set length low
mov al,ch
out dx,al ; set length high
pop dx
mov al,dl
mov dx,[DmaPageReg]
out dx,al ; set DMA page reg
mov al,[DMA_Channel]
and al,00000011b
out 0D4h,al ; unmask (activate) dma channel
pop bx
ret
ENDP
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
; This routine programs the DMAC for channels 0-7
;
; IN: [DMA_Channel], [DMAbaseAdd], [DMApageReg] must be setup
; [DAMBaseAdd] = Memory Address port
;
; dh = mode
; ax = address
; cx = length
; dl = page
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
PROC Prog_DMA NEAR
push bx
mov bx,ax
cmp [DMA_Channel],4
jb @@DoDMA03
mov al,[DMA_Channel]
out 0D4h,al ; mask reg bit
sub al,al
out 0D8h,al ; clr byte ptr
mov al,[DMA_Channel]
sub al,4
add al,dh
out 0D6h,al ; set mode reg
push dx
mov dx,[DMAbaseAdd]
mov al,bl
out dx,al ; set base address low
mov al,bh
out dx,al ; set base address high
add dl,2 ;point to length
mov al,cl
out dx,al ; set length low
mov al,ch
out dx,al ; set length high
pop dx
mov al,dl
mov dx,[DmaPageReg]
out dx,al ; set DMA page reg
mov al,[DMA_Channel]
and al,00000011b
out 0D4h,al ; unmask (activate) dma channel
pop bx
ret
@@DoDMA03:
mov al,4
add al,[DMA_Channel]
out 0Ah,al ; mask reg bit
sub al,al
out 0Ch,al ; clr byte ptr
mov al,dh
add al,[DMA_Channel]
out 0Bh,al ; set mode reg
push dx
mov dx,[DMAbaseAdd]
mov al,bl
out dx,al ; set base address low
mov al,bh
out dx,al ; set base address high
inc dx ;point to length
mov al,cl
out dx,al ; set length low
mov al,ch
out dx,al ; set length high
pop dx
mov al,dl
mov dx,[DmaPageReg]
out dx,al ; set DMA page reg
mov al,[DMA_Channel]
out 0Ah,al ; unmask (activate) dma channel
pop bx
ret
ENDP

View File

@@ -0,0 +1,291 @@
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
<20> Programming the Intel 8253 Programmable Interval Timer <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Written for the PC-GPE by Mark Feldman
e-mail address : u914097@student.canberra.edu.au
myndale@cairo.anu.edu.au
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD> Disclaimer <20>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD> Introduction <20>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
The PIT chip has 3 channels, each of which are responsible for a different
task on the PC:
Channel 0 is responsible for updating the system clock. It is usually
programmed to generate around 18.2 clock ticks a second. An interrupt 8 is
generated for every clock tick.
Channel 1 controls DMA memory refreshing. DRAM is cheap, but it's memory
cells must be periodically refreshed or they quickly lose their charge. The
PIT chip is responsible for sending signals to the DMA chip to refresh
memory. Most machines are refreshed at a higher rate than neccesary, and
reprogramming channel 1 to refresh memory at a slower rate can sometime speed
up system performance. I got a 2.5 MHz speed-up when I did it to my 286, but
it didn't seem to work on my 486SUX33.
Channel 2 is connected to the speaker. It's normally programmed to generate
a square wave so a continuous tone is heard. Reprogramming it for "Interrupt
on Terminal Count" mode is a nifty trick which can be used to play 8-bit
samples from the PC speaker.
Each channel has a counter which counts down. The PIT input frequency is
1193181 ($1234DD) Hz. Each counter decrements once for every input clock
cycle. "Terminal Count", mentioned several times below, is when the counter
reaches 0.
Loading the counters with 0 has the same effect as loading them with 10000h,
and is the highest count possible (approx 18.2 Hz).
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD> The PIT Ports <20>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
The PIT chip is hooked up to the Intel CPU through the following ports:
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
<20> Port Description <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ
<20> 40h Channel 0 counter (read/write) <20>
<20> 41h Channel 1 counter (read/write) <20>
<20> 42h Channel 2 counter (read/write) <20>
<20> 43h Control Word (write only) <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD> The Control Word <20>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
<20> 7 <20> 6 <20> 5 <20> 4 <20> 3 <20> 2 <20> 1 <20> 0 <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> BCD 0 - Binary 16 bit
<20> <20> <20> 1 - BCD 4 decades
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ <20> <20>
<EFBFBD> Select Counter <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Mode Number 0 - 5
<EFBFBD> 0 - Select Counter 0 <20> <20>
<EFBFBD> 1 - Select Counter 1 <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
<EFBFBD> 2 - Select Counter 2 <20> <20> <20> Read/Load <20>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20> 0 - Counter Latching <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ 1 - Read/Load LSB only <20>
<20> 2 - Read/Load MSB only <20>
<20> 3 - Read/Load LSB then MSB <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD> The PIT Modes <20>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
The PIT is capable of operating in 6 different modes:
MODE 0 - Interrupt on Terminal Count
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
When this mode is set the output will be low. Loading the count register
with a value will cause the output to remain low and the counter will start
counting down. When the counter reaches 0 the output will go high and remain
high until the counter is reprogrammed. The counter will continue to count
down after terminal count is reached. Writing a value to the count register
during counting will stop the counter, writing a second byte starts the
new count.
MODE 1 - Programmable One-Shot
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
The output will go low once the counter has been loaded, and will go high
once terminal count has been reached. Once terminal count has been reached
it can be triggered again.
MODE 2 - Rate Generator
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
A standard divide-by-N counter. The output will be low for one period of the
input clock then it will remain high for the time in the counter. This cycle
will keep repeating.
MODE 3 - Square Wave Rate Generator
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Similar to mode 2, except the ouput will remain high until one half of the
count has been completed and then low for the other half.
MODE 4 - Software Triggered Strobe
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
After the mode is set the output will be high. Once the count is loaded it
will start counting, and will go low once terminal count is reached.
MODE 5 - Hardware Triggered Strobe
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Hardware triggered strobe. Similar to mode 5, but it waits for a hardware
trigger signal before starting to count.
Modes 1 and 5 require the PIT gate pin to go high in order to start
counting. I'm not sure if this has been implemented in the PC.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD> Counter Latching <20>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Setting the Read/Load field in the Control Word to 0 (Counter Latch) causes
the appropriate channel to go into a sort of "lap" mode, the counter keeps
counting down internally but it appears to have stopped if you read it's
values through the channel's counter port. In this way you get a stable count
value when you read the counter. Once you send a counter latch command you
*must* then read the counter.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD> Doing Something Useful <20>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Ok, so let's say we are writing a game and we need to have a certain
routine called 100 times a second and we want to use channel 0 to do all
this timing in the background while the main program is busy doing other
stuff.
The first thing we have to realise is that BIOS usually uses channel 0 to
keep track of the time, so we have 3 options:
1) Have our own routine handle all timer interrupts. This will effectively
stop the PC clock and the system time will be wrong from that point on.
The clock will be reset to the proper time the next time the computer
is turned off and on again, but it's not a nice thing to do to someone
unless you really have to.
2) Have our routine do whatever it has to do and then call the BIOS handler.
This would be fine if our program was receiving the usual 18.2 ticks
a second, but we need 100 a second and calling the BIOS handler for every
tick will speed up the system time. Same net result as case 1.
3) Have our routine do the interrupt handling and call the BIOS handler only
when it needs to be updated! BINGO!
The PIT chip runs at a freqency of 1234DDh Hz, and normally the BIOS timer
interrupt handler is called for every 10000h cycles of this clock. First we
need to reprogram channel 0 to generate an interrupt 100 times a second, ie
every 1234DDh / 100 = 11931 cycles. The best thing to do is keep a running
total of the number of clock ticks which have occurred. For every interrupt
generated we will add 11931 to this total. When it reaches 10000h our handler
will know it's time to tell BIOS about it and do so.
So let's get into some good old Pascal code. First we'll define a few
constants and variables our program will need:
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
Uses Crt, Dos;
{$F+} { Force far mode, a good idea when mucking around with interrupts }
const TIMERINTR = 8;
PIT_FREQ = $1234DD;
var BIOSTimerHandler : procedure;
clock_ticks, counter : longint;
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
The clock_ticks variable will keep track of how many cycles the PIT has
had, it'll be intialised to 0. The counter variable will hold the new
channel 0 counter value. We'll also be adding this number to clock_ticks
every time our handler is called.
Next we need to do some initialization:
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
procedure SetTimer(TimerHandler : pointer; frequency : word);
begin
{ Do some initialization }
clock_ticks := 0;
counter := $1234DD div frequency;
{ Store the current BIOS handler and set up our own }
GetIntVec(TIMERINTR, @BIOSTimerHandler);
SetIntVec(TIMERINTR, TimerHandler);
{ Set the PIT channel 0 frequency }
Port[$43] := $34;
Port[$40] := counter mod 256;
Port[$40] := counter div 256;
end;
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Pretty straightforward stuff. We save the address of the BIOS handler,
install our own, set up the variables we'll use and program PIT channel 0
for the divide-by-N mode at the frequency we need.
This next bit is what we need to do once our program is finished. It just
resets everything back to normal.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
procedure CleanUpTimer;
begin
{ Restore the normal clock frequency }
Port[$43] := $34;
Port[$40] := 0;
Port[$40] := 0;
{ Restore the normal ticker handler }
SetIntVec(TIMERINTR, @BIOSTimerHandler);
end;
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Ok, here's our actual handler. This particular handler just writes an
asterix (*) to the screen. Then it does the checks to see if the BIOS
handler should be called. If so it calls it, if not it acknowledges the
interrupt itself.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
procedure Handler; Interrupt;
begin
{ DO WHATEVER WE WANT TO DO IN HERE }
Write('*');
{ Adjust the count of clock ticks }
clock_ticks := clock_ticks + counter;
{ Is it time for the BIOS handler to do it's thang? }
if clock_ticks >= $10000 then
begin
{ Yep! So adjust the count and call the BIOS handler }
clock_ticks := clock_ticks - $10000;
asm pushf end;
BIOSTimerHandler;
end
{ If not then just acknowledge the interrupt }
else
Port[$20] := $20;
end;
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
And finally our calling program. What follows is just an example program
which sets everything up, waits for us to press a key and then cleans up
after itself.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
begin
SetTimer(Addr(Handler), 100);
ReadKey;
CleanUpTimer;
end.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD> References <20>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Title : "Peripheral Components"
Publisher : Intel Corporation
ISBN : 1-55512-127-6

View File

@@ -0,0 +1,263 @@
Some remarks on the usage of the Real Time Clock (RTC) MC 146818 of the AT.
Not as comprehensive as The_Serial_Port, but I'm working on it :-).
Chris
--------------------------------------------------------------------------
You should use the BIOS interrupt 1Ah to program the RTC. If you don't
like this or if you want to use some special features, here is how to
access the registers directly:
Reading an RTC register
-----------------------
cli ; make sure no interrupt wants to access the RTC in the mean time
mov al,address
out 70h,al
in al,71h
sti
_disable();
outp(0x70,address);
x=inp(0x71);
_enable();
Writing an RTC register
-----------------------
cli
mov al,address
out 70h,al
mov al,value
out 71h,al
sti
_disable();
outp(0x70,address);
outp(0x71,value);
_enable();
Make sure you rewrite the address before every access, even if it's identical
with the previous access! CPU interrupts should be disabled during your
access, but of course you don't need to do that explicitly every time if they
are disabled anyway (eg. in an interrupt handler).
The RTC generates interrupt 70h. You must enable level 2 of the master ICU and
level 0 of the slave ICU. (Clear bit 2 of port 21h and bit 0 of port 0A1h;
both registers are read/write). Both master and slave ICU expect an EOI (write
20h to 20h and 0A0h).
And these are the registers:
Address Function
--------------------------------------
0 actual second
1 alarm second
2 actual minute
3 alarm minute
4 actual hour
5 alarm hour
6 day of week (not used by the BIOS nor DOS)
7 day of month
8 month
9 year
A status A
B status B
C status C
D status D
E - 3F buffered memory that holds the BIOS setup
40 - 7F buffered memory available with some clones
status A (Control 1)
--------
Bit 0-3: interrupt frequency (PC: 0110b = 1024 ints per second)
4-6: xtal frequency (PC: 010b = 32,768 kHz)
7: UIP (update in progess: if 1, time is invalid)
UIP tells you when you can't read the registers 0, 2, 4, 6 - 9 at the
moment. It is usually high for less than a millisecond.
You can change bits 0-6. These are the values that produce useful results
in a PC:
Bits 6-4
--------
010 use 32,768 Hz xtal
others don't do anything useful in PCs, really...
Bits 3-0
--------
0000 seems to inhibit the whole RTC
0001 divides xtal by 128 (PC: 256 ints per second)
0010 divides xtal by 256 (PC: 128 ints per second)
0011 divides xtal by 4 (PC: 8192 ints per second)
0100 divides xtal by 8 (PC: 4096 ints per second)
0101 divides xtal by 16 (PC: 2048 ints per second)
0110 divides xtal by 32 (PC: 1024 ints per second)
0111 divides xtal by 64 (PC: 512 ints per second)
1000 divides xtal by 128 (PC: 256 ints per second)
1001 divides xtal by 256 (PC: 128 ints per second)
1010 divides xtal by 512 (PC: 64 ints per second)
1011 divides xtal by 1024 (PC: 32 ints per second)
1100 divides xtal by 2048 (PC: 16 ints per second)
1101 divides xtal by 4096 (PC: 8 ints per second)
1110 divides xtal by 8192 (PC: 4 ints per second)
1111 divides xtal by 16384 (PC: 2 ints per second)
Obviously you should only use values 0011 - 1111. Note also that
8192 ints per second is an awful lot for slow computers.
status B (Control 2)
--------
Bit 0: 1=daylight savings flag (German "Sommerzeit"-"summer time")
1: 0=12hr, 1=24hr mode (PC: 1)
2: 0=BCD, 1=binary mode (PC: 0)
3: 1=square wave generator on (PC: 0)
4: 1=generate time update interrupts (every second) (PC: 0)
5: 1=alarm interrupt (int when alarm time reached) (PC: 0)
6: 1=generate periodic interrupt (see status A) (PC: 0)
7: 1=inhibit time increment (while setting the clock)
status C (interrupt cause)
--------
Bit 4: 1=time changed (generated every second)
5: 1=alarm time reached
6: 1=periodic int
others not defined
status D (battery)
--------
Bit 7: 1=battery OK, 0=battery weak
others undefined
When servicing interrupts, you MUST read status C! That's the chip's inter-
rupt acknowledge. Make sure to read this register after programming the
interrupt control register (status B), or you won't get any interrupts.
The Day Of Week - Counter counts from 1 to 7 and restarts with 1 again. When
0, it is not incremented. On a PC, it's always 0 if you don't set it to a
specific value. (Ralf Brown says 1=sunday, but not with my PCs.)
Using the BIOS instead
======================
[sneaked from Ralf Brown's interrupt list version 31]
----------1A02-------------------------------
INT 1A - TIME - GET REAL-TIME CLOCK TIME (AT,XT286,PS)
AH = 02h
Return: CF clear if successful
CH = hour (BCD)
CL = minutes (BCD)
DH = seconds (BCD)
DL = daylight savings flag (00h standard time, 01h daylight time)
CF set on error (i.e. clock not running or in middle of update)
SeeAlso: AH=00h
----------1A03-------------------------------
INT 1A - TIME - SET REAL-TIME CLOCK TIME (AT,XT286,PS)
AH = 03h
CH = hour (BCD)
CL = minutes (BCD)
DH = seconds (BCD)
DL = daylight savings flag (00h standard time, 01h daylight time)
SeeAlso: AH=01h
----------1A04-------------------------------
INT 1A - TIME - GET REAL-TIME CLOCK DATE (AT,XT286,PS)
AH = 04h
Return: CF clear if successful
CH = century (BCD)
CL = year (BCD)
DH = month (BCD)
DL = day (BCD)
CF set on error
SeeAlso: AH=02h,AH=05h,INT 21/AH=2Ah
----------1A05-------------------------------
INT 1A - TIME - SET REAL-TIME CLOCK DATE (AT,XT286,PS)
AH = 05h
CH = century (BCD)
CL = year (BCD)
DH = month (BCD)
DL = day (BCD)
SeeAlso: AH=04h,INT 21/AH=2Bh
----------1A06-------------------------------
INT 1A - TIME - SET ALARM (AT,XT286,PS)
AH = 06h
CH = hour (BCD)
CL = minutes (BCD)
DH = seconds (BCD)
Return: CF set on error (alarm already set or clock stopped for update)
CF clear if successful
Note: the alarm occurs every 24 hours until turned off, invoking INT 4A each
time
SeeAlso: AH=07h,INT 4A
----------1A07-------------------------------
INT 1A - TIME - CANCEL ALARM (AT,XT286,PS)
AH = 07h
Return: alarm disabled
Note: does not disable the real-time clock's IRQ
SeeAlso: AH=06h,INT 70
If you wish to reset the system clock according to the RTC, call int 21h
function 2Dh. Note that starting with DOS 3.3, this also influences the
RTC!! See below.
----------212C-------------------------------
INT 21 - DOS 1+ - GET SYSTEM TIME
AH = 2Ch
Return: CH = hour
CL = minute
DH = second
DL = 1/100 seconds
Note: on most systems, the resolution of the system clock is about 5/100sec,
so returned times generally do not increment by 1
on some systems, DL may always return 00h
SeeAlso: AH=2Ah,AH=2Dh,AH=E7h,INT 1A/AH=00h,INT 1A/AH=02h,INT 1A/AH=FEh
SeeAlso: INT 2F/AX=120Dh
----------212D-------------------------------
INT 21 - DOS 1+ - SET SYSTEM TIME
AH = 2Dh
CH = hour
CL = minute
DH = second
DL = 1/100 seconds
Return: AL = result
00h successful
FFh invalid time, system time unchanged
Note: DOS 3.3+ also sets CMOS clock
SeeAlso: AH=2Bh"DOS",AH=2Ch,INT 1A/AH=01h,INT 1A/AH=03h,INT 1A/AH=FFh"AT&T"
A better method to reset the system clock is to calculate the number of
timer clicks since midnight and write it to the BIOS variable 0h:46Ch
(or call int 1Ah). This does not affect the precision of the RTC like calls
to 212D do.
0h:46Ch DWORD Timer ticks since midnight
0h:470h BYTE Timer overflow, non-zero if has counted past midnight
----------1A00-------------------------------
INT 1A - TIME - GET SYSTEM TIME
AH = 00h
Return: CX:DX = number of clock ticks since midnight
AL = midnight flag, nonzero if midnight passed since time last read
Notes: there are approximately 18.2 clock ticks per second, 1800B0h per 24 hrs
IBM and many clone BIOSes set the flag for AL rather than incrementing
it, leading to loss of a day if two consecutive midnights pass
without a request for the time (e.g. if the system is on but idle)
SeeAlso: AH=01h,AH=02h,INT 21/AH=2Ch
----------1A01-------------------------------
INT 1A - TIME - SET SYSTEM TIME
AH = 01h
CX:DX = number of clock ticks since midnight
SeeAlso: AH=00h,AH=03h,INT 21/AH=2Dh
BTW: the exact value is 18.2064819335 clicks per second (or 1193180/65536).
You can calculate these values without floating point arithmetics!
Ralf Brown's interrupt list is available from several ftp sites. Ask an archie
server for "prog inter3". I think the actual version is 38.

View File

@@ -0,0 +1,413 @@
Real Time Clock / CMOS Setup Reference
Version: 10 March 1993
By Tom Przeor
AT model was the first in IBM PC family to keep track of time while
switched off. The designers used Motorola MC146818 Real Time Clock
(RTC from now on) chip. This chip provides clock and calendar
functions, few registers to program the chip itself and some 50
bytes of general purpose memory. Typically the RTC chip is used only
on power on - the BIOS will initialize the DOS clock and verify the
configuration. The RTC chip is capable of generating interrupts at
specified frequency or time - we'll get back to it later.
RTC BIOS interface.
-------------------
AT BIOS provides a number of basic functions to use RTC. The
following short list should provide enough information to use them:
RAM data areas used by RTC:
Addr hex Size
0040:0098 4 bytes far pointer to user wait flag
0040:009C 4 bytes wait count
0040:00A0 1 byte wait active flag:
bit 7 - 1 when wait time elapsed
bit 0 - 1 when wait active
bits 6-1 - reserved
Int 1Ah function 02h - Get RTC time
entry: AH = 02h
exit : CF clear if successful, set on error
CH = hour (BCD)
CL = minutes (BCD)
DH = seconds (BCD)
DL = daylight savings flag
(00h standard time, 01h daylight time)
Int 1Ah function 03h - Set RTC time
entry: AH = 03h
CH = hour (BCD)
CL = minutes (BCD)
DH = seconds (BCD)
DL = daylight savings flag (as above)
exit: none
Int 1Ah function 04h - Get RTC date
entry: AH = 04h
exit: CF clear if successful, set on error
CH = century (BCD)
CL = year (BCD)
DH = month (BCD)
DL = day (BCD)
Int 1Ah function 05h - Set RTC date
entry: AH = 05h
CH = century (BCD)
CL = year (BCD)
DH = month (BCD)
DL = day (BCD)
exit: none
Int 1Ah function 06h - Set RTC alarm
entry: AH = 06h
CH = hour (BCD)
CL = minutes (BCD)
DH = seconds (BCD)
exit: CF clear if successful, set on error
note: place address for alarm routine in interrupt 4Ah
vector before using this service, the alarm occurs
every 24 hours until turned off, invoking int 4Ah
each time.
Int 1Ah function 07h - Reset RTC alarm
entry: AH = 07h
exit: none
note: disables alarm set with int1Ah/fn 06h. Don't forget
to restore old int 4Ah vector.
Int 15h function 83h - Set/Cancel Wait Interval
subfunction 0 - Set Wait Interval
entry: AH = 83h
AL = 00h
CX:DX = microseconds to delay
ES:BX -> byte whose high bit is to be set at end of
interval
exit: CF clear if successful, set on error
Int 15h function 83h - Set/Cancel Wait Interval
subfunction 1 - Cancel Wait Interval
entry: AH = 83h
AL = 01h
exit: CF clear if successful, set on error
error status in AH:
80h invalid command (PC,PCjr)
86h function not supported (XT and later)
Int 15h function 86h - Wait
entry: AH = 86h
CX:DX = interval in microseconds
exit: CF clear if successful (wait interval elapsed)
CF set on error or AH=83h wait already in progress
There is a difference between functions 83h and 86h. Function 83h
sets a wait interval and allows processing to continue. When the
wait interval ends the user specified flag (pointed by ES:BX) is set
and it is software responsibility to check that flag. That function
can be stopped. Function 86h on the other hand suspends any
processing until specified time interval is elapsed (and cannot be
stopped). These two functions share the same data areas and cannot
be used at the same time. Also note that their resolution is 977
microseconds.
Direct Access to RTC chip.
--------------------------
RTC chip can be accessed through I/O ports 70h and 71h. To read a
byte from the chip, do an OUT 70h,addr; followed by IN al,71H. To
write a byte to chip, do an OUT 70h,addr; followed by OUT 71h,value.
Example: read equipment byte from CMOS info
mov al,14h ;register 14h holds equipment byte
out 70h,al ;select address 14h on RTC chip
jmp $+2 ;a slight delay to settle things
in al,71h ;AL now has equipment byte
* NOTE: Original MC146818 has 64 registers (00h to 3Fh). Most of the
computers used today have a RTC functional equivalent incorporated
in their 'chipset' and it can have more registers. Those extra bits
are often used by chipset and BIOS designers to store extra
information about things like DRAM wait states, refresh, m/b cache
or user defined hard drive parameters - don't fiddle with them or
you might end up in trouble. Also leave alone the reserved bytes.
The RTC Registers.
------------------
The registers can be divided into three functional groups:
1. Clock/calendar - updated from on chip clock, on IBM compatibles
all quantities are stored in BCD format (ie. 23dec is stored 23h).
2. Status - they affect working of RTC chip itself.
3. CMOS configuration data - general purpose memory not affected and
not affecting the RTC chip.
Here is detailed list of registers (all byte sized, addr in hex):
Addr Function
==== =========================================
** clock/calendar
00 current second for real-time clock
01 alarm second
02 current minute
03 alarm minute
04 current hour
05 alarm hour
06 current day of week (1=Sunday)
07 current date of month
08 current month
09 current year (final two digits; eg, 93)
** status
0A Status Register A - Read/Write except UIP
== =========================================
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
<20> 7 <20> 6 <20> 5 <20> 4 <20> 3 <20> 2 <20> 1 <20> 0 <20>
<20> UIP <20> DV2 <20> DV1 <20> DV0 <20> RS3 <20> RS2 <20> RS1 <20> RS0 <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
bit 7 - UIP flag, Update In Progress. When set an update
cycle is in progress and the clock/calendar cannot be
accessed. When clear, at least 244 microseconds are
available to access clock/calendar bytes (it's plenty of
time even on 6MHz AT).
bits 6-4 - divider bits that define RTC operating frequency.
ATs have a 32.768 kHz (wrist watch) crystal to operate RTC
and divider should be set to '010', other values will make a
time machine from your computer.
bits 3-0 - Rate Selection bits that define the periodic
interrupt rate, see another table for details. Default value
set by BIOS is '0110'.
0B Status Register B - Read/Write
== ==============================
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
<20> 7 <20> 6 <20> 5 <20> 4 <20> 3 <20> 2 <20> 1 <20> 0 <20>
<20> SET <20> PIE <20> AIE <20> UIE <20> SQWE<57> DM <20>24/12<31> DSE <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
bit 7 (SET) - when set to 1, any update in progress is
aborted and a program may initialize the
clock/calendar/alarm bytes without an update occurring.
Setting this bit clears UIE (bit 4). Clearing bit 7 allows
the update cycle to continue.
bit 6 (PIE) - Periodic Interrupt Enable, when set the
periodic interrupt will occur at the frequency specified by
RS bits in Status Register A.
bit 5 (AIE) - Alarm Interrupt Enable, when set the alarm
interrupt will be asserted once for each second that the
current time matches the alarm time.
bit 4 (UIE) - Update-ended Interrupt Enable, when set the
update-ended interrupt will be asserted once each second
after the end of update cycle. This bit is cleared when SET
bit goes high but it is not reset when SET is cleared.
bit 3 (SQWE) - Square Wave Enable, when set, enables the
square wave output on the SQW pin at the frequency specified
by the RS bits in the Status Register A. The SQW pin is not
connected to anything in the AT.
bit 2 (DM) - Data Mode, indicates mode for clock/calendar
data: 0=BCD and 1=binary, BIOS setting is 0.
bit 1 (24/12) - controls hours byte, 0=12-hour and 1=24-hour
format, BIOS setting is 1.
bit 0 (DSE) - Daylight Savings Enable, when set two special
updates will occur: last Sunday in April time will go
01:59:59 > 03:00:00 and last Sunday in October 01:59:59 >
01:00:00. BIOS sets it to 0 (ie. no daylight saving).
0C Status Register C - Read-only
== =============================
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
<20> 7 <20> 6 <20> 5 <20> 4 <20> 3 <20> 2 <20> 1 <20> 0 <20>
<20> IRQF<51> PF <20> AF <20> UF <20> 0 <20> 0 <20> 0 <20> 0 <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
bit 7 (IRQF) - Interrupt Request Flag, when set one of the
interrupts enabled in Status Register B has occurred.
bit 6 (PF) - Periodic interrupt Flag, when set the periodic
interrupt has occurred.
bit 5 (AF) - Alarm interrupt Flag, when set the alarm
interrupt has occurred.
bit 4 (UF) - Update-ended interrupt Flag, when set the
update-ended alarm interrupt has occurred.
NOTE: PF, AF, UF are set regardless of corresponding enable
bits in Status Register B. IRQF will be set only if the
interrupt flag and its corresponding enable bit are set.
These four flags are cleared each time Status Register C is
read.
bits 3-0 - reserved, always 0.
0D Status Register D - Read-only
== =============================
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
<20> 7 <20> 6 <20> 5 <20> 4 <20> 3 <20> 2 <20> 1 <20> 0 <20>
<20> VRT <20> 0 <20> 0 <20> 0 <20> 0 <20> 0 <20> 0 <20> 0 <20>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
bit 7 (VRT) - Valid RAM and Time, OK when set, when clear
indicates power was lost.
bits 6-0 - reserved.
** configuration
0E POST diagnostics status byte
== ============================
bit 7 = 1 clock lost power
bit 6 = 1 CMOS checksum bad
bit 5 = 1 invalid configuration found at POST
bit 4 = 1 memory size compare error at POST
bit 3 = 1 fixed disk or controller failed
bit 2 = 1 invalid RTC time (eg. 31 Feb)
bits 1-0 - reserved
0F Shutdown Status Byte
== ====================
This byte is read upon startup after CPU reset in order to
determine if the reset cause (to get out of protected mode
etc.)
00 - power on reset
(0 = soft reset (Ctrl-Alt-Del) or unexpected shutdown. Skip
POST) - conflicting info from older reference ??????
01 - memory size pass
02 - memory test pass
03 - memory test fail
04 - POST end, boot system
05 - JMP DWORD PTR 0:[0467h] with EOI (End Of Interrupt)
06 - protected tests pass
07 - protected tests fail
08 - memory size fail
09 - INT 15h block move
0A - JMP DWORD PTR 0:[0467h] without EOI
10 Diskette drive types
== ====================
bits 7-4 - drive 0 type (A:)
bits 3-0 - drive 1 type (B:)
0000b - no drive
0001b - 360k
0010b - 1.2M
0011b - 720k
0100b - 1.44M
11 Reserved
12 Hard disk drive type
== ====================
(for drives C: and D:, when between 1 and 14)
bits 7-4 - fixed disk 0 type (C:)
bits 3-0 - fixed disk 1 type (D:)
0000b = no drive
0001b-1110b = drive type
1111b = drive 0 (1) type stored at addr 19h (1Ah)
13 Reserved
14 Equipment byte
== ==============
bits 7-6 - no. of floppy drives (00=1, 01=2, 10=3, 11=4)
bits 5-4 - primary display 00 = none, EGA, VGA ...
01 = 40x25 colour
10 = 80x25 colour
11 = 80x25 monochrome
bits 3-2 - reserved
bit 1 =1 if math copro installed
bit 0 =1 if floppy drive(s) present
15 Base memory (low byte)
16 Base memory (high byte)
== =======================
in kbytes (eg. 0100H=256K, 0200H=512K, 0280H=640K)
17 Extended memory above 1M (low byte)
18 Extended memory (high byte) in kbytes
19 Disk 0 type if (CMOS addr 12H & 0fH) is 0fH
1A Disk 1 type if (CMOS addr 12H & f0H) is f0H
1B-2D Reserved
2E Checksum of CMOS addresses 10H through 20H (high byte)
2F Checksum of CMOS addresses 10H through 20H (low byte)
30 Actual extended memory size (low byte) ???
31 Actual extended memory size (high byte) ???
32 Century in BCD (eg. 19h)
33 Miscellaneous flags
bit 7 - IBM 128K memory option installed
bit 6 - used by "Setup" utility (?)
bits 5-0 - reserved
34-3F Reserved
Using RTC hardware interrupt.
-----------------------------
RTC interrupt pin is connected to IRQ8 line in AT bus and generates
int 70h when enabled. The chip can generate three different types of
interrupts: periodic, alarm and update-ended. To use RTC interrupt
first install interrupt service routine and point int 70h vector to
it, then program RTC status registers (details shortly) and 'unmask'
bit 0 of second PIC's mask register at port A1h. You can enable more
than one interrupt type at the same time, in that case your
interrupt handler should check which type has occurred (by reading
Status Register C).
Update-Ended Interrupt
======================
This is the simplest type - interrupt is generated after each clock
update exactly every 1 second. To enable set bit 4 (UIE) in Status
Register B.
Alarm Interrupt
===============
This is a second type - it generates interrupt at specified time. To
use it first set Alarm Seconds (addr 01h), Alarm Minute (addr 03h)
and Alarm Hour (addr 05h), then set bit 5 (AIE) in Status Register
B. The special value FFh in one of alarm registers will match any
time, eg. FF:FF:00 will generate alarm interrupt every minute,
FF:00:FF will generate interrupt every second during first minute of
every hour.
Periodic Interrupt
==================
The frequency of this interrupt is programmable from 2 to 8192 per
second. To use this type of interrupt first set RS (Rate Select)
bits in Status Register A to the required value:
RS Int/sec Period
3210 - -
0000 none none
0001 256 3.90625 ms
0010 128 7.8125 ms
0011 8192 122.070 Micros
0100 4096 244.141 Micros
0101 2048 488.281 Micros
0110 1024 976.562 Micros
0111 512 1.93125 ms
1000 256 3.90625 ms
1001 128 7.8125 ms
1010 64 15.625 ms
1011 32 31.25 ms
1100 16 62.50 ms
1101 8 125.0 ms
1110 4 250.0 ms
1111 2 500.0 ms
Note: usually this is set to 0110 by BIOS at boot up.
After setting RS bits set bit 6 (PIE) in Status Register B.

View File

@@ -0,0 +1,857 @@
Ok, here is some code I wrote a while ago, and touched up a little
recently as someone else needed it. As you may have noticed by the
line count, it is VERY wordy...hopefully some of it is useful.
This code returns a time value in units of 838 nanoseconds,
and I have found it quite useful. Even though the examples I
provide are for fixed-frame-rate games, I have used this in
a crude ray-casting (yuk) demo I wrote a while back which will
run as fast as your CPU (or monitor/video) will allow.
Lemme know if you create something cool with it!
Ethan -- rer@wlv.iipo.gtegsc.com
(following are timer.c and timer.h)
SNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIP
/***************************************************************************\
** TIMING CODE MODULE (80x86 specific code!) **
**=========================================================================**
** Written by Ethan Rohrer, (c) Nuthing Software, December 4, 1994 **
** **
** Revision History **
** ---------------- **
** Date Description **
** --------- --------------------------------------------------------- **
** 13 May 94 Initial Release **
** 04 Dec 94 Updated code to conform to current coding standards/style **
** Allowed appl. to specify # of timers as param. to TM_Init **
** Standardized error handling with function _TM_Error **
**=========================================================================**
** This file contains code which makes use of system timer 0. This timer **
** normally operates at a frequency of 1.1932MHz, regardless of the speed **
** of the CPU. Normally, this timer is operating in mode 3 (square wave **
** mode), and it completes a "cycle" in 0.054926 seconds (~1/18.207 **
** seconds). The reason I say "normally" is because it is possible to **
** change the length of the cycle, which changes the timer's frequency. **
** This is NOT what the following code does. **
** **
** System timer 0 has its own 16 bit counter. Here is some simplified **
** pseudo-code of what system timer 0 does with its counter when it is **
** operating in mode 3, and its frequency has not been tampered with: **
** **
** counter = 65536 **
** while (counter != 0) **
** counter -= 2; **
** counter = 65536 **
** while (counter != 0) **
** counter -= 2; **
** **
** You read that right. The counter is decremented from 65536 to 0 **
** TWICE. This is somewhat unfortunate, because we want to read that **
** counter to use for our timing operations because it is very accurate. **
** The counter is decremented 65536 times in one timer cycle (32768 **
** times in each while loop above). So, the time between decrements is: **
** **
** (0.054926 seconds/cycle) / (65536 decrements/cycle) = **
** 0.000000838 seconds/decrement = 838ns/decrement **
** **
** Since the counter is decremented to 0 twice in each timer cycle, we **
** would only be able to time events that take no longer than 0.027463 **
** seconds (one half of the timer cycle, the duration of one of the **
** while loops above). **
** **
** The solution used by the code in this file is to change the timer's **
** operation mode to mode 2. Here is some simplified pseudo-code of what **
** system timer 0 does with its counter when it is operating in mode 2, **
** and its frequency has not been tampered with: **
** **
** counter = 65536 **
** while (counter != 0) **
** counter -= 1; **
** **
** This solves any problems concerning the ambiguity of determining **
** which while loop is executing when we read the counter from the timer. **
** But now, we have a 16 bit value which can only be used to time events **
** which take no longer than 0.054926 seconds (duration of one cycle). **
** **
** The solution used by this code is to make use of another timer: **
** the "large timer", which is a 32-bit value at memory location **
** 0x40:0x6C. Conveniently, this "large timer" is incremented each time **
** system timer 0 completes a cycle (once each 0.054926 seconds). **
** The code in this file generates 32-bit values to represent the **
** time. Obviously, we can't pack in the 16 bit counter and the 32 bit **
** large timer into the 32 bit time type, so we cut off the high order **
** 16 bits of the large timer. The following picture describes how the **
** time value is generated using the timers: **
** **
** 31 0 15 0 **
** ------------------------------------------- ---------------------- **
** | L a r g e T i m e r | | 65535 - Counter | **
** ------------------------------------------- ---------------------- **
** | | | **
** \|/ \|/ \|/ **
** V V V **
** 31 16 15 0 **
** ---------------------- ---------------------- **
** | t T I M E | **
** ---------------------- ---------------------- **
** **
** (Note that we have to use (65535-Counter) because Counter is being **
** decremented by the timer, but time is increasing) **
** **
**=========================================================================**
** USING THIS MODULE **
** Before calling any other timing routine, you must call TM_Init(n), **
** where n specifies the number of timers your application needs. **
** It may be a good idea to call TM_Init() in the initialization **
** portion of your application. **
** **
** To begin timing an event, make a call to TM_StartTimer(tid), **
** where tid is an integer in the range [0..(n-1)] which specifies **
** which of the n timers you are starting. **
** **
** To compute the duration of the event, just call **
** TM_ElapsedTime(tid), where tid is the same integer used in the **
** call to TM_StartTimer(). **
** **
** When you are finished with the timing routines, call TM_Close(). **
** This should fit in nicely with the cleanup section of your **
** application. **
** **
** If your application NEEDS to handle the time computations itself, **
** the function TM_ReadTimer(tid) is also available. This function **
** will return the current time (MOD 1 hour, approximately). I **
** discourage use of this function, but discovered that I need it **
** for another module/library... **
** **
** EXAMPLES **
** A simple delaying routine: **
** void delay( tTIME duration ) **
** { **
** TM_StartTimer(0); **
** while (TM_ElapsedTime(0) < duration) **
** ; **
** } **
** **
** A fixed frame-rate game: **
** TM_Init(1); **
** while (player_not_dead) **
** { **
** TM_StartTimer(0); **
** MoveMonsters(); **
** MovePlayers(); **
** UpdateDisplay(); **
** while (TM_ElapsedTime(0) < frame_duration) **
** ; **
** } **
** TM_Close(); **
\***************************************************************************/
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include "timer.h"
/*-------------------------------- MACROS ---------------------------------*/
/* macro to return the current value of the large timer */
#define TM_LARGE_TIMER (*((unsigned long far *)MK_FP(0x40, 0x6C)))
/*--------------------------- GLOBAL VARIABLES ----------------------------*/
/* starting times for all of the timers this code will support */
tTIME *gpTM_start_time = NULL;
unsigned int gTM_max_timers = 0;
/* flag to let us know if it is ok to run the timing routines */
/* (1 = NOT safe, 0 = safe) */
unsigned char gTM_module_not_initialized = 1;
/*------------------------- Error Message Strings -------------------------*/
#define TM_ERR_STR_GENERAL \
"General (unspecified) error."
#define TM_ERR_STR_UNINITIALIZED \
"Timing routines not yet initialized.\n" \
"(TM_Init() has not been called)"
#define TM_ERR_STR_BAD_TIMER_ID \
"Application specified an invalid timer ID."
#define TM_ERR_STR_ALLOC \
"Unable to allocate dynamic memory."
#define TM_ERR_STR_ZERO_TIMERS \
"Application requested 0 timers.\n" \
"(must request 1 or more timers to use this module)"
/*------------------------- Error Message Indices -------------------------*/
/* (Make sure these indices are accurate according to gpTM_error_text[] */
/* declared below !!) */
#define TM_ERR_GENERAL 0
#define TM_ERR_UNINITIALIZED 1
#define TM_ERR_BAD_TIMER_ID 2
#define TM_ERR_ALLOC 3
#define TM_ERR_ZERO_TIMERS 4
/*------------------------- Error Message Strings -------------------------*/
/* (Make sure the positions of the error messages in this array are */
/* accurately represented by the error messages indices listed above !!) */
char *gpTM_error_text[] =
{
TM_ERR_STR_GENERAL,
TM_ERR_STR_UNINITIALIZED,
TM_ERR_STR_BAD_TIMER_ID,
TM_ERR_STR_ALLOC,
TM_ERR_STR_ZERO_TIMERS
};
/***************************************************************************\
** void _TM_Error( ) **
*****************************************************************************
** ARGUMENTS **
** const char *pCalling_function_name **
** (I) name of the calling function **
** int error_number **
** (I) integer identifier of the error that occurred **
** const char *pCustom_message **
** (I) additional message text to be displayed **
**-------------------------------------------------------------------------**
** RETURNS **
** void **
**-------------------------------------------------------------------------**
** EXAMPLE USAGE (NOT INTENDED FOR EXTERNAL USE) **
** if ( gTM_module_not_initialized ) **
** { **
** _TM_Error ( pFunction_name, TM_ERR_UNINITIALIZED, NULL ); <-<< **
** return; **
** } **
**-------------------------------------------------------------------------**
** DETECTABLE ERROR CONDITIONS **
** None **
**-------------------------------------------------------------------------**
** DESCRIPTION **
** This function will generate a message which will be sent to stderr **
** to inform the user of an error. This message will include the **
** name of the function the error occurred in (if supplied), a canned **
** error string for the error indicated, and a custom string (if **
** supplied) which may provide more details about th error. **
**-------------------------------------------------------------------------**
** LIMITATIONS **
** The message text must not exceed 1024 bytes in size, which can **
** store over 12 80-character lines of text. **
\***************************************************************************/
void _TM_Error ( pCalling_function_name, error_number, pCustom_message )
const char *pCalling_function_name;
int error_number;
const char *pCustom_message;
{
char error_message[1024]; /* buffer for message text */
/*---------------------------------------------------------------------*\
** Insert the "ERROR IN MODULE "TIMER"" header string into our **
** message. **
\*---------------------------------------------------------------------*/
sprintf ( error_message,
"\n******** ERROR IN MODULE \"TIMER\" *********\n" );
/*---------------------------------------------------------------------*\
** Insert the name of the function in which the error was discovered. **
** This should always be provided, but check for NULL to be safe. **
\*---------------------------------------------------------------------*/
strcat ( error_message, "FUNCTION: " );
if ( pCalling_function_name != (char *)NULL )
strcat ( error_message, pCalling_function_name );
else
strcat ( error_message, "<not specified - kill the programmer>" );
strcat ( error_message, "\n" );
/*---------------------------------------------------------------------*\
** Insert the canned error message text for the specified error **
** number. **
\*---------------------------------------------------------------------*/
strcat ( error_message, gpTM_error_text[error_number] );
strcat ( error_message, "\n" );
/*---------------------------------------------------------------------*\
** Insert the custom_message, if it is supplied. This custom message **
** should provide more detailed information than the generic error **
** strings. **
\*---------------------------------------------------------------------*/
if ( pCustom_message != (char *)NULL )
{
strcat ( error_message, pCustom_message );
} /* end if ( custom message was supplied ) */
strcat ( error_message, "\n" );
/*---------------------------------------------------------------------*\
** Send the message off to stderr. **
\*---------------------------------------------------------------------*/
fprintf ( stderr, "%s", error_message );
} /* end _TM_Error ( ) */
/***************************************************************************\
** tTIME TM_ReadTime( ) **
*****************************************************************************
** ARGUMENTS **
** void **
**-------------------------------------------------------------------------**
** RETURNS **
** tTIME - the current time measured in units of 838ns **
**-------------------------------------------------------------------------**
** EXAMPLE USAGE (NOT INTENDED FOR EXTERNAL USE) **
** InitializeApplication(); **
** TM_Init(1); **
** . . . **
** current_time = TM_ReadTime( ); <-----<< **
** . . . **
** TM_Close(); **
** ShutDownApplication(); **
**-------------------------------------------------------------------------**
** DETECTABLE ERROR CONDITIONS **
** * Module has not been initialized (TM_Init() not called) **
**-------------------------------------------------------------------------**
** DESCRIPTION **
** This function generates a 32-bit value in units of 838ns. This **
** value spans a time period of about one hour. The high 16 bits of **
** this value come from the large timer (the 32-bit value at **
** 0x40:0x6C, the number of timer cycles since midnight), and the **
** low 16 bits come from system timer 0's counter. **
** **
** NOTE: System timer 0's counter repeatedly cycles from 65535 down **
** to 0 (decremented), but time is steadily increasing. To **
** get a steadily increasing value from this timer, we subtract **
** the actual value of the counter from 65535. **
**-------------------------------------------------------------------------**
** LIMITATIONS **
** The value returned by this function is the current time MOD **
** (approximately) one hour, not the time of day. It is only useful **
** useful for timing events that finish within an hour. **
\***************************************************************************/
tTIME TM_ReadTime ( void )
{
register unsigned char LSB; /* least significant byte */
register unsigned char MSB; /* most significant byte */
char *pFunction_name = /* This function's name, used for */
"TM_ReadTime()"; /* error reporting */
/*---------------------------------------------------------------------*\
** Handle uninitialized module error. **
\*---------------------------------------------------------------------*/
if ( gTM_module_not_initialized )
{
_TM_Error ( pFunction_name, TM_ERR_UNINITIALIZED, NULL );
return( (tTIME) (-1) );
} /* end if */
/*---------------------------------------------------------------------*\
** By writing 0x00 to port 0x43, we are specifying the following **
** command: **
** bits 76 = 00 --> timer 0 **
** bits 54 = 00 --> reading 16-bit value: lsb followed by msb **
** bits 321 = 000 --> ignored **
** bit 0 = 0 --> ignored **
\*---------------------------------------------------------------------*/
outportb ( 0x43, 0x00 );
/*---------------------------------------------------------------------*\
** The following two statements reading from port 0x40 are reading **
** system timer 0's 16-bit counter: MSB after LSB. **
\*---------------------------------------------------------------------*/
LSB = inportb ( 0x40 ); /* get low order byte */
MSB = inportb ( 0x40 ); /* get high order byte */
/*---------------------------------------------------------------------*\
** Pack the time into the 32-bit tTIME return value. **
** (see the preamble at the top of this file for more details) **
\*---------------------------------------------------------------------*/
return( (unsigned long int) (TM_LARGE_TIMER << 16)
| (unsigned int) (0xFFFF-((((unsigned int)MSB)<<8)|LSB)));
} /* end TM_ReadTime() */
/***************************************************************************\
** void TM_StartTimer( tid ) **
*****************************************************************************
** ARGUMENTS **
** unsigned int tid **
** (I) integer label, which is used as an index into the array **
** of timers (gpTM_start_time[]) **
**-------------------------------------------------------------------------**
** RETURNS **
** void **
**-------------------------------------------------------------------------**
** EXAMPLE USAGE **
** #define THIRTYITH_OF_A_SECOND 39759 **
** #define DELAY_TIMER 0 **
** InitializeApplication(); **
** TM_Init(1); **
** . . . **
** TM_StartTimer(DELAY_TIMER); <-----<< **
** MoveMonsters(); **
** MovePlayers(); **
** UpdateDisplay(); **
** while (TM_ElapsedTime(DELAY_TIMER) < THIRTYITH_OF_A_SECOND) **
** ; **
** . . . **
** TM_Close(); **
** ShutDownApplication(); **
**-------------------------------------------------------------------------**
** DETECTABLE ERROR CONDITIONS **
** * Module has not been initialized (TM_Init() not called) **
** * tid is out of range (range = [0..(gTM_max_timers-1)]) **
**-------------------------------------------------------------------------**
** DESCRIPTION **
** This procedure starts a pseudo-timer by reading the current time **
** and storing it in the global array gpTM_start_time[]. **
**-------------------------------------------------------------------------**
** LIMITATIONS **
** None **
\***************************************************************************/
void TM_StartTimer ( tid )
unsigned int tid;
{
char custom_message[256]; /* Buffer for custom error message */
char *pFunction_name = /* This function's name, used for */
"TM_StartTimer()"; /* error reporting */
/*---------------------------------------------------------------------*\
** Handle uninitialized module error. **
\*---------------------------------------------------------------------*/
if (gTM_module_not_initialized)
{
_TM_Error ( pFunction_name, TM_ERR_UNINITIALIZED, NULL );
return;
} /* end if */
if ( tid < gTM_max_timers ) /* then tid is valid */
{
/*-----------------------------------------------------------------*\
** The timer ID is valid, so mark the starting time for this **
** timer (tid) **
\*-----------------------------------------------------------------*/
gpTM_start_time[tid] = TM_ReadTime ( );
}
else /* tid is out of range */
{
/*-----------------------------------------------------------------*\
** Handle the bad timer ID error. **
\*-----------------------------------------------------------------*/
sprintf ( custom_message, "Request received for timer %u, but the "
"last valid timer is timer %u.\n",
tid, gTM_max_timers-1 );
_TM_Error ( pFunction_name, TM_ERR_BAD_TIMER_ID, custom_message );
} /* end if */
} /* end TM_StartTimer() */
/***************************************************************************\
** tTIME TM_ElapsedTime( tid ) **
*****************************************************************************
** ARGUMENTS **
** unsigned int tid **
** (I) integer label, which is used as an index into the array of **
** timers (gpTM_start_time[]) **
**-------------------------------------------------------------------------**
** RETURNS **
** tTIME - the amount of time that has passed since the last call **
** to TM_StartTimer(tid), measured in units of 838ns **
**-------------------------------------------------------------------------**
** EXAMPLE USAGE: **
** #define THIRTYITH_OF_A_SECOND 39759 **
** #define DELAY_TIMER 0 **
** InitializeApplication(); **
** TM_Init(1); **
** . . . **
** TM_StartTimer(DELAY_TIMER); **
** MoveMonsters(); **
** MovePlayers(); **
** UpdateDisplay(); **
** while (TM_ElapsedTime(DELAY_TIMER) < THIRTYITH_OF_A_SECOND) <---<< **
** ; **
** . . . **
** TM_Close(); **
** ShutDownApplication(); **
**-------------------------------------------------------------------------**
** DETECTABLE ERROR CONDITIONS **
** * Module has not been initialized (TM_Init() not called) **
** * tid is out of range (range = [0..(gTM_max_timers-1)]) **
**-------------------------------------------------------------------------**
** DESCRIPTION **
** This function calculates the time that has elapsed for timer slot **
** tid since the last call to TM_StartTimer(tid) by getting the **
** current time and subtracting the starting time (stored in global **
** array gpTM_start_time[].) **
**-------------------------------------------------------------------------**
** LIMITATIONS **
** The largest elapsed time that can accurately be measured is **
** approximately one hour: **
** (tTIME = unsigned long int = 32-bits) **
** (65536(ticks/cycle)*18.2(cycles/sec)*3600 ~= 2^32) **
\***************************************************************************/
tTIME TM_ElapsedTime ( tid )
unsigned int tid;
{
char custom_message[256]; /* Buffer for custom error message */
char *pFunction_name = /* This function's name, used for */
"TM_ElapsedTime()"; /* error reporting */
/*---------------------------------------------------------------------*\
** Handle uninitialized module error. **
\*---------------------------------------------------------------------*/
if ( gTM_module_not_initialized )
{
_TM_Error( pFunction_name, TM_ERR_UNINITIALIZED, NULL );
return( (tTIME) (-1) );
} /* end if */
if ( tid < gTM_max_timers ) /* then tid is valid */
{
/*-----------------------------------------------------------------*\
** The timer ID is valid, so compute the elapsed time. **
\*-----------------------------------------------------------------*/
return( TM_ReadTime ( ) - gpTM_start_time[tid] );
}
else /* tid is out of range */
{
/*-----------------------------------------------------------------*\
** Handle the bad timer ID error. **
\*-----------------------------------------------------------------*/
sprintf ( custom_message, "Request received for timer %u, but the "
"last valid timer is timer %u.\n",
tid, gTM_max_timers-1 );
_TM_Error ( pFunction_name, TM_ERR_BAD_TIMER_ID, custom_message );
return( (tTIME) (-1) );
} /* end if */
} /* end TM_ElapsedTime() */
/***************************************************************************\
** int TM_Init( ) **
*****************************************************************************
** ARGUMENTS **
** unsigned int num_timers **
** (I) number of timers the application is requesting **
**-------------------------------------------------------------------------**
** RETURNS **
** int - non-zero means an error occurred **
** 0 if the timer module was successfully initialized **
**-------------------------------------------------------------------------**
** EXAMPLE USAGE **
** InitializeApplication(); **
** TM_Init(1); <-----<< **
** . . . **
** ApplicationBody(); **
** . . . **
** TM_Close(); **
** ShutDownApplication(); **
**-------------------------------------------------------------------------**
** DETECTABLE ERROR CONDITIONS **
** * Invalid number of timers requested (0) **
** * Failed to allocated memory for array of pseudo-timers **
**-------------------------------------------------------------------------**
** DESCRIPTION **
** This procedure sets timer 0 to operation mode 2, and allocates **
** memory to store starting times for the specified number of **
** (pseudo)timers. **
** **
** The gTM_module_not_initialized is set to 0 to express that the **
** module has been initialized, so the code can be used correctly. **
**-------------------------------------------------------------------------**
** LIMITATIONS **
** None **
\***************************************************************************/
int TM_Init ( num_timers )
unsigned int num_timers;
{
char custom_message[256]; /* Buffer for custom error message */
char *pFunction_name = /* This function's name, used for */
"TM_Init()"; /* error reporting */
/*---------------------------------------------------------------------*\
** Make sure this code doesn't get executed more than once before **
** TM_Close(). We would allocate a new array of timers, and lose **
** the previously allocated array (memory leak). **
\*---------------------------------------------------------------------*/
if ( !gTM_module_not_initialized )
{
return ( 0 ); /* return success */
/* (this isn't an error, just stupid) */
}
/*---------------------------------------------------------------------*\
** Allocate an array of qseudo-timers **
\*---------------------------------------------------------------------*/
if ( num_timers > 0 )
{
gpTM_start_time = (tTIME *) calloc ( num_timers, sizeof ( tTIME ) );
if ( gpTM_start_time == NULL )
{
sprintf ( custom_message,
"Failed to allocate %u timers of size %u.\n",
num_timers, (unsigned int) sizeof ( tTIME ) );
_TM_Error ( pFunction_name, TM_ERR_ALLOC, custom_message );
return ( -1 );
} /* end if */
gTM_max_timers = num_timers;
}
else /* num_timers == 0, since num_timers is an unsigned int */
{
/*-----------------------------------------------------------------*\
** Application requested 0 timers, which is pointless. **
\*-----------------------------------------------------------------*/
_TM_Error ( pFunction_name, TM_ERR_ZERO_TIMERS, NULL );
return ( -1 );
} /* end if-else */
/*---------------------------------------------------------------------*\
** Set timer to operation mode 2 **
** **
** By writing 0x34 to port 0x43, we are specifying: **
** (0x34 = 00110100) **
** bits 76 = 00 --> timer 0 **
** bits 54 = 11 --> writing 16-bit value: lsb followed by msb **
** bits 321 = 010 --> operation mode 2 **
** bit 0 = 0 --> binary counter operation **
\*---------------------------------------------------------------------*/
outportb ( 0x43, 0x34 );
/*---------------------------------------------------------------------*\
** The following two statements writing 0x00 to port 0x40 are writing **
** a 16-bit value of 0x0000 to the timer's counter, which specifies **
** that the counter is to begin its cycle with a value of 65536 **
** (0x10000), which specifies a maximum length cycle (54.926ms). **
\*---------------------------------------------------------------------*/
outportb ( 0x40, 0x00 );
outportb ( 0x40, 0x00 );
/*---------------------------------------------------------------------*\
** Unlock this module **
\*---------------------------------------------------------------------*/
gTM_module_not_initialized = 0;
return ( 0 ); /* initialization successful */
} /* TM_Init() */
/***************************************************************************\
** void TM_Close( ) **
*****************************************************************************
** ARGUMENTS **
** void **
**-------------------------------------------------------------------------**
** RETURNS **
** void **
**-------------------------------------------------------------------------**
** EXAMPLE USAGE **
** InitializeApplication(); **
** TM_Init(1); **
** . . . **
** ApplicationBody(); **
** . . . **
** TM_Close(); <-----<< **
** ShutDownApplication(); **
**-------------------------------------------------------------------------**
** DETECTABLE ERROR CONDITIONS **
** * Module has not been initialized (TM_Init() not called) **
**-------------------------------------------------------------------------**
** DESCRIPTION **
** This procedure returns system timer 0 to operation mode 3, and **
** deallocates the memory used to store the starting times for each **
** of the (pseudo)timers. **
** **
** The gTM_module_not_initialized is set to 1 to prevent further use **
** of this module. This module will remain locked until TM_Init() **
** is called. **
**-------------------------------------------------------------------------**
** LIMITATIONS **
** None **
\***************************************************************************/
void TM_Close ( void )
{
char *pFunction_name = /* This function's name, used for */
"TM_Close()"; /* error reporting */
/*---------------------------------------------------------------------*\
** Handle uninitialized module error. **
\*---------------------------------------------------------------------*/
if ( gTM_module_not_initialized )
{
_TM_Error ( pFunction_name, TM_ERR_UNINITIALIZED, NULL );
return;
} /* end if */
/*---------------------------------------------------------------------*\
** Deallocate the array of pseudo-timers **
\*---------------------------------------------------------------------*/
if ( gpTM_start_time != NULL )
{
free ( gpTM_start_time );
gpTM_start_time = NULL;
} /* end if */
/*---------------------------------------------------------------------*\
** Set timer to operation mode 3 **
** **
** By writing 0x36 to port 0x43, we are specifying: **
** (0x36 = 00110110) **
** bits 76 = 00 --> timer 0 **
** bits 54 = 11 --> writing 16-bit value: lsb followed by msb **
** bits 321 = 011 --> operation mode 3 **
** bit 0 = 0 --> binary counter operation **
\*---------------------------------------------------------------------*/
outportb ( 0x43, 0x36 );
/*---------------------------------------------------------------------*\
** The following two statements writing 0x00 to port 0x40 are writing **
** a 16-bit value of 0x0000 to the timer's counter, which specifies **
** that the counter is to begin its cycle with a value of 65536 **
** (0x10000), which specifies a maximum length cycle (54.926ms). **
\*---------------------------------------------------------------------*/
outportb ( 0x40, 0x00 );
outportb ( 0x40, 0x00 );
/*---------------------------------------------------------------------*\
** Lock this module **
\*---------------------------------------------------------------------*/
gTM_module_not_initialized = 1;
} /* end TM_Close ( ) */
SNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIPSNIP
/***************************************************************************\
** TIMING CODE MODULE **
**=========================================================================**
** Written by Ethan Rohrer, (c) Nuthing Software, December 4, 1994 **
** **
** Revision History **
** ---------------- **
** Date Description **
** --------- --------------------------------------------------------- **
** 13 May 94 Initial Release **
** 04 Dec 94 Updated code to conform to current coding standards/style **
** Allowed appl. to specify # of timers as param. to TM_Init **
** Standardized error handling with function _TM_Error **
**=========================================================================**
** This file contains the declarations for publicly available types and **
** functions. **
**=========================================================================**
** USING THIS MODULE **
** Before calling any other timing routine, you must call TM_Init(n), **
** where n specifies the number of timers your application needs. **
** It may be a good idea to call TM_Init() in the initialization **
** portion of your application. **
** **
** To begin timing an event, make a call to TM_StartTimer(tid), **
** where tid is an integer in the range [0..(n-1)] which specifies **
** which of the n timers you are starting. **
** **
** To compute the duration of the event, just call **
** TM_ElapsedTime(tid), where tid is the same integer used in the **
** call to TM_StartTimer(). **
** **
** When you are finished with the timing routines, call TM_Close(). **
** This should fit in nicely with the cleanup section of your **
** application. **
** **
** If your application NEEDS to handle the time computations itself, **
** the function TM_ReadTimer(tid) is also available. This function **
** will return the current time (MOD 1 hour, approximately). I **
** discourage use of this function, but discovered that I need it **
** for another module/library... **
** **
** EXAMPLES **
** A simple delaying routine: **
** (TM_Init() must be called before this routine!) **
** void delay( tTIME duration ) **
** { **
** TM_StartTimer(0); **
** while (TM_ElapsedTime(0) < duration) **
** ; **
** } **
** **
** A fixed frame-rate game: **
** TM_Init(1); **
** while (player_not_dead) **
** { **
** TM_StartTimer(0); **
** MoveMonsters(); **
** MovePlayers(); **
** UpdateDisplay(); **
** while (TM_ElapsedTime(0) < frame_duration) **
** ; **
** } **
** TM_Close(); **
\***************************************************************************/
#ifndef __TIMER_H__
#define __TIMER_H__
/*------------------------------ Types ------------------------------------*/
/* type used to store time: should be 32-bits */
typedef unsigned long int tTIME;
/*----------------------------- Defines -----------------------------------*/
/* maximum number of timers to allow */
#define TM_MAX_TIMERS 1
/* just a handy constant */
#define ONE_SECOND 1193200 /* 65536(ticks/cycle) * 18.207(cycles/sec) */
/*------------------------- Public Prototypes -----------------------------*/
extern void TM_Close( );
extern tTIME TM_ElapsedTime( );
extern int TM_Init( );
extern tTIME TM_ReadTime( );
extern void TM_StartTimer( );
#endif /* __TIMER_H__ */

View File

@@ -0,0 +1,192 @@
The Timer!!!
------------
The system timer interrupt (Int 08) is controlled by an 8253 timer chip. This
chip has three outputs, and is also used to drive the memory refresh circuitry
and the speaker.
The timer output frequency is obtained by dividing the 8253 master clock
frequency of 1,193,180 Hz by a 16 bit divisor. (A zero divisor is treated as
65,536. This is the value programmed by the BIOS, to give a system timer of
18.2065 Hz.) The timer is programmed by sending a control byte to port 43h,
followed by a divisor word (low byte, high byte) to port 4Xh where X is the
timer being programmed.
If the sytem timer (counter 0) is modified, an Int 8 handler must be written to
call the original Int 8 handler 18.2 times per second, otherwise some system
functions (eg time of day) may be upset. When it does call the original Int 8
handler it must NOT send and EOI to the 8259 for the timer interrupt, since the
original Int 8 handler will send the EOI also.
Sample code follows:
.radix 16
ratio equ 4 ;ratio of new freq to old freq
;an integer, to keep it simple
; Change timer frequency and revector interrupt
mov ax,3508 ;get original timer vector
int 21
mov word ptr org_08,bx
mov word ptr org_08+2,es
mov ax,2508 ;set new timer vector
mov dx,offset int_08
int 21
cli
mov al,36 ;control byte
out 43,al
mov al,low (10000/ratio) ;LSB of divisor
out 40,al
mov al,high (10000/ratio) ;MSB of divisor
out 40,al
sti
; Restore timer frequency and interrupt vector
cli
mov al,36 ;control byte
out 43,al
mov al,0 ;LSB of divisor (0 for 65536d)
out 40,al
out 40,al ;MSB of divisor (0 for 65536d)
sti
mov ax,2508 ;set new timer vector
push ds
mov dx,word ptr org_08
mov ds,word ptr org_08+2
int 21
pop ds
; New Int 08 interrupt code
; Note that all other (lower priority) interrupts are disabled by the PIC
; until EOI is sent at the _end_ of the routine
int_08: push ax
; insert interrupt code here
dec cs:count ;call original interrupt as required
jz oldint
eoi: mov al,20 ;if not, send EOI to PIC
out 20,al
pop ax
iret
oldint: mov cs:count,ratio ;reset counter
pop ax
jmp far cs:[org_08] ;original vector will send EOI to PIC
; End of sample code
The following is from:
HelpPC 2.10, Quick Reference Utility, Copyright 1991 David Jurgens
8253/8254 PIT - Programmable Interval Timer
Port 40h, 8253 Counter 0 Time of Day Clock (normally mode 3)
Port 41h, 8253 Counter 1 RAM Refresh Counter (normally mode 2)
Port 42h, 8253 Counter 2 Cassette and Speaker Functions
Port 43h, 8253 Mode Control Register, data format:
|7|6|5|4|3|2|1|0| Mode Control Register
| | | | | | | +---- 0=16 binary counter, 1=4 decade BCD counter
| | | | +-+-+----- counter mode bits
| | +-+---------- read/write/latch format bits
+-+------------- counter select bits (also 8254 read back command)
Bits
76 Counter Select Bits
00 select counter 0
01 select counter 1
10 select counter 2
11 read back command (8254 only, illegal on 8253, see below)
Bits
54 Read/Write/Latch Format Bits
00 latch present counter value
01 read/write of MSB only
10 read/write of LSB only
11 read/write LSB, followed by write of MSB
Bits
321 Counter Mode Bits
000 mode 0, interrupt on terminal count; countdown, interrupt,
then wait for a new mode or count; loading a new count in the
middle of a count stops the countdown
001 mode 1, programmable one-shot; countdown with optional
restart; reloading the counter will not affect the countdown
until after the following trigger
010 mode 2, rate generator; generate one pulse after 'count' CLK
cycles; output remains high until after the new countdown has
begun; reloading the count mid-period does not take affect
until after the period
011 mode 3, square wave rate generator; generate one pulse after
'count' CLK cycles; output remains high until 1/2 of the next
countdown; it does this by decrementing by 2 until zero, at
which time it lowers the output signal, reloads the counter
and counts down again until interrupting at 0; reloading the
count mid-period does not take affect until after the period
100 mode 4, software triggered strobe; countdown with output high
until counter zero; at zero output goes low for one CLK
period; countdown is triggered by loading counter; reloading
counter takes effect on next CLK pulse
101 mode 5, hardware triggered strobe; countdown after triggering
with output high until counter zero; at zero output goes low
for one CLK period
Read Back Command Format (8254 only)
|7|6|5|4|3|2|1|0| Read Back Command (written to Mode Control Reg)
| | | | | | | +--- must be zero
| | | | | | +---- select counter 0
| | | | | +----- select counter 1
| | | | +------ select counter 2
| | | +------- 0 = latch status of selected counters
| | +-------- 0 = latch count of selected counters
+-+--------- 11 = read back command
Read Back Command Status (8254 only, read from counter register)
|7|6|5|4|3|2|1|0| Read Back Command Status
| | | | | | | +--- 0=16 binary counter, 1=4 decade BCD counter
| | | | +-+-+---- counter mode bits (see Mode Control Reg above)
| | +-+--------- read/write/latch format (see Mode Control Reg)
| +------------ 1=null count (no count set), 0=count available
+------------- state of OUT pin (1=high, 0=low)
- the 8253 is used on the PC & XT, while the 8254 is used on the AT+
- all counters are decrementing and fully independent
- the PIT is tied to 3 clock lines all generating 1.19318 MHz.
- the value of 1.19318MHz is derived from (4.77/4 MHz) and has it's
roots based on NTSC frequencies
- counters are 16 bit quantities which are decremented and then
tested against zero. Valid range is (0-65535). To get a value
of 65536 clocks you must specify 0 as the default count since
65536 is a 17 bit value.
- reading by latching the count doesn't disturb the countdown but
reading the port directly does; except when using the 8254 Read
Back Command
- counter 0 is the time of day interrupt and is generated
approximately 18.2 times per sec. The value 18.2 is derived from
the frequency 1.19318/65536 (the normal default count).
- counter 1 is normally set to 18 (dec.) and signals the 8237 to do
a RAM refresh approximately every 15us
- counter 2 is normally used to generate tones from the speaker
but can be used as a regular counter when used in conjunction
with the 8255
- newly loaded counters don't take effect until after an output
pulse or input CLK cycle depending on the mode
- the 8253 has a max input clock rate of 2.6MHz, the 8254 has max
input clock rate of 10MHz
Mitch

View File

@@ -0,0 +1,706 @@
Tank Presents
=================
Subject: How to program the DMA Sections: 4
By: Breakpoint Appendicies: 2
Version: 1.2 Source Code: 16/32b C
Date: July 5, 1995 Level: Beg-Adv
[1] Introduction
=================
What is the DMA?
The DMA is another chip on your motherboard (usually is an Intel 8237
chip) that allows you (the programmer) to offload data transfers between
I/O boards. DMA actually stands for 'Direct Memory Access'.
An example of DMA usage would be the Sound Blaster's ability to play
samples in the background. The CPU sets up the sound card and the DMA. When
the DMA is told to 'go', it simply shovels the data from RAM to the card.
Since this is done off-CPU, the CPU can do other things while the data is
being transferred.
If you're going to be programming the DMA in 32-bit protected mode, there
are a few extra things you're going to need to learn. If you already know the
basics of the DMA, you might want to skip to Appendix A (near the end of this
file) where I tell you how to do everything with the DMA in 32-bit mode.
Lastly, if you're interested in what I know about programming the DMA to
do memory to memory transfers, you might want to refer to Appendix B. This
section is by no means complete, and it will probably be added to in the
future as I learn more about this particular type of transfer.
Allright, here's how you program the DMA chip.
[2] DMA Basics
===============
When you want to start a DMA transfer, you need to know three things:
- Where the memory is located (what page),
- The offset into the page, and
- How much you want to transfer.
Since the DMA can work in both directions (memory to I/O card, and I/O
card to memory), you can see how the Sound Blaster can record as well as
play by using DMA.
The DMA has two restrictions which you must abide by:
- You cannot transfer more than 64K of data in one shot, and
- You cannot cross a page boundary.
Restriction #1 is rather easy to get around. Simply transfer the first
block, and when the transfer is done, send the next block.
For those of you not familiar with pages, I'll try to explain.
Picture the first 1MB region of memory in your system. It is divided
into 16 pages of 64K a piece like so:
Page Segment:Offset address
---- ----------------------
0 0000:0000 - 0000:FFFF
1 1000:0000 - 1000:FFFF
2 2000:0000 - 2000:FFFF
3 3000:0000 - 3000:FFFF
4 4000:0000 - 4000:FFFF
5 5000:0000 - 5000:FFFF
6 6000:0000 - 6000:FFFF
7 7000:0000 - 7000:FFFF
8 8000:0000 - 8000:FFFF
9 9000:0000 - 9000:FFFF
A A000:0000 - A000:FFFF
B B000:0000 - B000:FFFF
C C000:0000 - C000:FFFF
D D000:0000 - D000:FFFF
E E000:0000 - E000:FFFF
F F000:0000 - F000:FFFF
This might look a bit overwhelming. Not to worry if you're a C
programmer, as I'm going to assume you know the C language for the examples
in this text. All the code in here will compile with Turbo C 2.0.
Okay, remember the three things needed by the DMA? Look back if you
need to. We can stuff this data into a structure for easy accessing:
--------------------------------------------------------------------------------
typedef struct
{
char page;
unsigned int offset;
unsigned int length;
} DMA_block;
--------------------------------------------------------------------------------
Now, how do we find a memory pointer's page and offset? Easy. Use
the following code:
--------------------------------------------------------------------------------
void LoadPageAndOffset(DMA_block *blk, char *data)
{
unsigned int temp, segment, offset;
unsigned long foo;
segment = FP_SEG(data);
offset = FP_OFF(data);
blk->page = (segment & 0xF000) >> 12;
temp = (segment & 0x0FFF) << 4;
foo = offset + temp;
if (foo > 0xFFFF)
blk->page++;
blk->offset = (unsigned int)foo;
}
--------------------------------------------------------------------------------
Most (if not all) of you are probably thinking, "What the heck is he doing
there?" I'll explain.
The FP_SEG and FP_OFF macros find the segment and the offset of the data
block in memory. Since we only need the page (look back at the table above),
we can take the upper 4 bits of the segment to create our page.
The rest of the code takes the segment, adds the offset, and sees if the
page needs to be advanced or not. (Note that a memory region can be located at
2FFF:F000, and a single byte increase will cause the page to increase by one.)
In plain English, the page is the highest 4 bits of the absolute 20 bit
address of our memory location. The offset is the lower 12 bits of the
absolute 20 bit address plus our offset.
Now that we know where our data is, we need to find the length.
The DMA has a little quirk on length. The true length sent to the DMA
is actually length + 1. So if you send a zero length to the DMA, it actually
transfers one byte, whereas if you send 0xFFFF, it transfers 64K. I guess
they made it this way because it would be pretty senseless to program the
DMA to do nothing (a length of zero), and in doing it this way, it allowed a
full 64K span of data to be transferred.
Now that you know what to send to the DMA, how do you actually start it?
This enters us into the different DMA channels.
[3] DMA channels
=================
The DMA has 4 different channels to send 8-bit data. These channels are
0, 1, 2, and 3, respectively. You can use any channel you want, but if you're
transferring to an I/O card, you need to use the same channel as the card.
(ie: Sound Blaster uses DMA channel 1 as a default.)
There are 3 ports that are used to set the DMA channel:
- The page register,
- The address (or offset) register, and
- The word count (or length) register.
The following chart will describe each channel and it's corresponding
port number:
DMA Channel Page Address Count
------------------------------------
0 87h 0h 1h
1 83h 2h 3h
2 81h 4h 5h
3 82h 6h 7h
4 8Fh C0h C2h
5 8Bh C4h C6h
6 89h C8h CAh
7 8Ah CCh CEh
(Note: Channels 4-7 are 16-bit DMA channels. See below for more info.)
Since you need to send a two-byte value to the DMA (the offset and the
length are both two bytes), the DMA requests you send the low byte of data
first, then the high byte. I'll give a thorough example of how this is done
momentarily.
The DMA has 3 registers for controlling it's state. Here is the bitmap
layout of how they are accessed:
--------------------------------------------------------------------------------
Mask Register (0Ah):
=====================
MSB LSB
x x x x x x x x
------------------- - -----
| | | 00 - Select channel 0 mask bit
| | \---- 01 - Select channel 1 mask bit
| | 10 - Select channel 2 mask bit
| | 11 - Select channel 3 mask bit
| |
| \---------- 0 - Clear mask bit
| 1 - Set mask bit
|
\----------------------- xx - Don't care
Mode Register (0Bh):
=====================
MSB LSB
x x x x x x x x
\---/ - - ----- -----
| | | | | 00 - Channel 0 select
| | | | \---- 01 - Channel 1 select
| | | | 10 - Channel 2 select
| | | | 11 - Channel 3 select
| | | |
| | | | 00 - Verify transfer
| | | \------------ 01 - Write transfer
| | | 10 - Read transfer
| | |
| | \-------------------- 0 - Autoinitialized
| | 1 - Non-autoinitialized
| |
| \------------------------ 0 - Address increment select
|
| 00 - Demand mode
\------------------------------ 01 - Single mode
10 - Block mode
11 - Cascade mode
DMA clear selected channel (0Ch):
==================================
Outputting a zero to this port stops all DMA processes that are currently
happening as selected by the mask register (0Ah).
--------------------------------------------------------------------------------
Some of the most common modes to program the mode register are:
- 45h: Write transfer (I/O card to memory), and
- 49h: Read transfer (memory to I/O card).
Both of these assume DMA channel 1 for all transfers.
Now, there's also the 16-bit DMA channels as well. These shove two bytes
of data at a time. That's how the Sound Blaster 16 works as well in 16-bit
mode.
Programming the DMA for 16-bits is just as easy as 8 bit transfers. The
only difference is you send data to different I/O ports. The 16-bit DMA also
uses 3 other control registers as well:
--------------------------------------------------------------------------------
Mask Register (D4h):
=====================
MSB LSB
x x x x x x x x
------------------- - -----
| | | 00 - Select channel 4 mask bit
| | \---- 01 - Select channel 5 mask bit
| | 10 - Select channel 6 mask bit
| | 11 - Select channel 7 mask bit
| |
| \---------- 0 - Clear mask bit
| 1 - Set mask bit
|
\----------------------- xx - Don't care
Mode Register (D6h):
=====================
MSB LSB
x x x x x x x x
----- - - ----- -----
| | | | | 00 - Channel 4 select
| | | | \---- 01 - Channel 5 select
| | | | 10 - Channel 6 select
| | | | 11 - Channel 7 select
| | | |
| | | | 00 - Verify transfer
| | | \------------ 01 - Write transfer
| | | 10 - Read transfer
| | |
| | \-------------------- 0 - Autoinitialized
| | 1 - Non-autoinitialized
| |
| \------------------------ 0 - Address increment select
|
| 00 - Demand mode
\------------------------------ 01 - Single mode
10 - Block mode
11 - Cascade mode
DMA clear selected channel (D8h):
==================================
Outputting a zero to this port stops all DMA processes that are currently
happening as selected by the mask register (D4h).
--------------------------------------------------------------------------------
Now that you know all of this, how do you actually use it? Here is sample
code to program the DMA using our DMA_block structure we defined before.
V--------------------------------------------------------------------------------
/* Just helps in making things look cleaner. :) */
typedef unsigned char uchar;
typedef unsigned int uint;
/* Defines for accessing the upper and lower byte of an integer. */
#define LOW_BYTE(x) (x & 0x00FF)
#define HI_BYTE(x) ((x & 0xFF00) >> 8)
/* Quick-access registers and ports for each DMA channel. */
uchar MaskReg[8] = { 0x0A, 0x0A, 0x0A, 0x0A, 0xD4, 0xD4, 0xD4, 0xD4 };
uchar ModeReg[8] = { 0x0B, 0x0B, 0x0B, 0x0B, 0xD6, 0xD6, 0xD6, 0xD6 };
uchar ClearReg[8] = { 0x0C, 0x0C, 0x0C, 0x0C, 0xD8, 0xD8, 0xD8, 0xD8 };
uchar PagePort[8] = { 0x87, 0x83, 0x81, 0x82, 0x8F, 0x8B, 0x89, 0x8A };
uchar AddrPort[8] = { 0x00, 0x02, 0x04, 0x06, 0xC0, 0xC4, 0xC8, 0xCC };
uchar CountPort[8] = { 0x01, 0x03, 0x05, 0x07, 0xC2, 0xC6, 0xCA, 0xCE };
void StartDMA(uchar DMA_channel, DMA_block *blk, uchar mode)
{
/* First, make sure our 'mode' is using the DMA channel specified. */
mode |= DMA_channel;
/* Don't let anyone else mess up what we're doing. */
disable();
/* Set up the DMA channel so we can use it. This tells the DMA */
/* that we're going to be using this channel. (It's masked) */
outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
/* Clear any data transfers that are currently executing. */
outportb(ClearReg[DMA_channel], 0x00);
/* Send the specified mode to the DMA. */
outportb(ModeReg[DMA_channel], mode);
/* Send the offset address. The first byte is the low base offset, the */
/* second byte is the high offset. */
outportb(AddrPort[DMA_channel], LOW_BYTE(blk->offset));
outportb(AddrPort[DMA_channel], HI_BYTE(blk->offset));
/* Send the physical page that the data lies on. */
outportb(PagePort[DMA_channel], blk->page);
/* Send the length of the data. Again, low byte first. */
outportb(CountPort[DMA_channel], LOW_BYTE(blk->length));
outportb(CountPort[DMA_channel], HI_BYTE(blk->length));
/* Ok, we're done. Enable the DMA channel (clear the mask). */
outportb(MaskReg[DMA_channel], DMA_channel);
/* Re-enable interrupts before we leave. */
enable();
}
void PauseDMA(uchar DMA_channel)
{
/* All we have to do is mask the DMA channel's bit on. */
outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
}
void UnpauseDMA(uchar DMA_channel)
{
/* Simply clear the mask, and the DMA continues where it left off. */
outportb(MaskReg[DMA_channel], DMA_channel);
}
void StopDMA(uchar DMA_channel)
{
/* We need to set the mask bit for this channel, and then clear the */
/* selected channel. Then we can clear the mask. */
outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
/* Send the clear command. */
outportb(ClearReg[DMA_channel], 0x00);
/* And clear the mask. */
outportb(MaskReg[DMA_channel], DMA_channel);
}
uint DMAComplete(uchar DMA_channel)
{
/* Register variables are compiled to use registers in C, not memory. */
register int z;
z = CountPort[DMA_channel];
outportb(0x0C, 0xFF);
/* This *MUST* be coded in Assembly! I've tried my hardest to get it */
/* into C, and I've had no success. :( (Well, at least under Borland.) */
redo:
asm {
mov dx,z
in al,dx
mov bl,al
in al,dx
mov bh,al
in al,dx
mov ah,al
in al,dx
xchg ah,al
sub bx,ax
cmp bx,40h
jg redo
cmp bx,0FFC0h
jl redo
}
return _AX;
}
--------------------------------------------------------------------------------
I think all the above functions are self explanatory except for the last
one. The last function returns the number of bytes that the DMA has
transferred to (or read from) the device. I really don't know how it works
as it's not my code. I found it laying on my drive, and I thought it might
be somewhat useful to those of you out there. You can find out when a DMA
transfer is complete this way if the I/O card doesn't raise an interrupt.
DMAComplete() will return -1 (or 0xFFFF) if there is no DMA in progress.
Don't forget to load the length into your DMA_block structure as well
before you call StartDMA(). (When I was writing these routines, I forgot
to do that myself... I was wondering why it was transferring garbage.. <G>)
[4] Conclusion
===============
I hope you all have caught on to how the DMA works by now. Basically it
keeps a list of DMA channels that are running or not. If you need to change
something in one of these channels, you mask the channel, and reprogram. When
you're done, you simply clear the mask, and the DMA starts up again.
If anyone has problems getting this to work, I'll be happy to help. Send
us mail at the address below, and either I or another Tank member will fix
your problem(s).
Enjoy!
- Breakpoint
[A] Programming the DMA in 32-bit protected mode
=================================================
Programming the DMA in 32-bit mode is a little trickier than in 16-bit
mode. One restriction you have to comply with is the 1 Mb DOS barrier.
Although the DMA can access memory up to the 16 Mb limit, most I/O devices
can't go above the 1 Mb area. Knowing this, we simply default to living
with the 1 Mb limit.
Since your data you want to transfer is probably somewhere near the end
of your RAM (Watcom allocates memory top-down), you won't have to worry about
not having room in the 1 Mb area.
So, how do you actually allocate a block of RAM in the 1 Mb area? Simple.
Make a DPMI call -- or better yet, use the following functions to do it for
you. :)
--------------------------------------------------------------------------------
typedef struct
{
unsigned int segment;
unsigned int offset;
unsigned int selector;
} RMptr;
RMptr getmem(int size)
{
union REGS regs;
struct SREGS sregs;
RMptr foo;
segread(&sregs);
regs.w.ax = 0x0100;
regs.w.bx = (size+15) >> 4;
int386x(0x31, &regs, &regs, &sregs);
foo.segment = regs.w.ax;
foo.offset = 0;
foo.selector = regs.w.dx;
return foo;
}
void freemem(RMptr foo)
{
union REGS regs;
struct SREGS sregs;
segread(&sregs);
regs.w.ax = 0x0101;
regs.w.dx = foo.selector;
int386x(0x31, &regs, &regs, &sregs);
}
void rm2pmcpy(RMptr from, char *to, int length)
{
char far *pfrom;
pfrom = (char far *)MK_FP(from.selector, 0);
while (length--)
*to++ = *pfrom++;
}
void pm2rmcpy(char *from, RMptr to, int length)
{
char far *pto;
pto = (char far *)MK_FP(to.selector, 0);
while (length--)
*pto++ = *from++;
}
--------------------------------------------------------------------------------
Take note on a couple of things here. First of all, the getmem() function
does exactly what it says, along with freemem(). But remember, you're not
tossing around a pointer anymore. It's just a data structure with a segment
and an offset stored in it.
You've allocated your memory, and now you need to put something into it.
You need to use pm2rmcpy() to copy protected mode memory to real mode memory.
If you want to go the other way, rm2pmcpy() is there to help you.
Now we need to load the DMA_block with our information since we now have
data that the DMA can access. The function is technically the same, but it
just handles different variables:
--------------------------------------------------------------------------------
void LoadPageAndOffset(DMA_block *blk, RMptr data)
{
unsigned int temp, segment, offset;
unsigned long foo;
segment = data.segment;
offset = data.offset;
blk->page = (segment & 0xF000) >> 12;
temp = (segment & 0x0FFF) << 4;
foo = offset + temp;
if (foo > 0xFFFF)
blk->page++;
blk->offset = (unsigned int)foo;
}
--------------------------------------------------------------------------------
That's about it. Since you've now loaded your DMA_block structure with
the data you need, the rest of the functions should work fine without any
problems. The only thing you'll need to concern yourself with is using
'_enable()' instead of 'enable()', '_disable()' instead of 'disable()', and
'outp()' instead of 'outportb()'.
[B] Doing memory to memory DMA transfers
==========================================
All information contained in this area is mostly theory and results of
tests I have done in this area. This is not a very well documented area,
and it is probably even less portable from machine to machine.
Welcome to the undocumented world of memory to memory DMA transfers! This
area has given me many headaches, so as a warning (and maybe preventive
medicine), you might want to take an aspirin or two before proceeding. :)
I will be writing on a level of medium intelligence. You should understand
the basics of DMA transfers, and at least understand 90%, if not all, of the
information contained in this document (except for this area, of course). You
won't find any source code here, however, I plan to release full source code
once I get the DMA to transfer a full block of memory to the video card (if
it's possible)...
Anyways, let's get started.
I recently set out on the task of figuring out how to transfer a single
area of memory to the video screen by using DMA.
When you sit down to think about it, it really does not seem to be too
difficult. You might think, 'All I need to do is use 2 DMA channels. One
set to read and one set to write. My video buffer will need to be aligned
onto a segment so the DMA can transfer the data without stopping.' This is
a good theory, but, unfortunately, it doesn't work. I'll show you (sort of)
why it doesn't work.
I originally started out with the idea that DMA channel 0 would read from
my video buffer aligned on a segment, and DMA channel 1 would write to the
video memory (at 0xA000).
In testing this simple idea, I wasn't suprised that nothing happened when
I enabled the DMA. After playing around with some of the registers for a
little bit, I opened the Undocumented DOS book and scanned the ports. Here's
a snippet of what I found:
--------------------------------------------------------------------------------
0008 w DMA channel 0-3 command register
bit 7 = 1 DACK sense active high
= 0 DACK sense active low
bit 6 = 1 DREQ sense active high
= 0 DREQ sense active low
bit 5 = 1 extended write selection
= 0 late write selection
bit 4 = 1 rotating priority
= 0 fixed priority
bit 3 = 1 compressed timing
= 0 normal timing
bit 2 = 1 enable controller
= 0 enable memory-to-memory
--------------------------------------------------------------------------------
Seeing bit 2 at port 0x08 made me realize that the DMA might possibly NOT
default to being able to handle memory to memory transfers.
Again, I tried my test program, and I still wasn't suprised that nothing
happened. I opened Undocumented DOS again, and found another port that I
skipped over:
--------------------------------------------------------------------------------
0009 DMA write request register
--------------------------------------------------------------------------------
After thinking for a little, I realized that even though the DMA is
enabled, the I/O card that you are usually transferring to must communicate
with the bus to tell the DMA it's ready to receive data. Since we have no
I/O card to say 'Go!', we need to set the DMA to 'Go!' manually.
Undocumented DOS had no bit flags defined for port 0x09, so here is what
I've been able to come up with thus far:
--------------------------------------------------------------------------------
DMA Write Request Register (09h):
==================================
MSB LSB
x x x x x x x x
------------------- - -----
| | | 00 - Select channel 0
| | \---- 01 - Select channel 1
| | 10 - Select channel 2
| | 11 - Select channel 3
| |
| \---------- 0 - ???
| 1 - Needs to be turned on
|
\----------------------- xx - Don't care
--------------------------------------------------------------------------------
After adding a couple of lines of code, and running the test program once
again, I was amazed to see that my screen cleared! I didn't get a buffer
copy, I got a screen clear. I went back into the code to make sure my buffer
had data, and sure enough, it did.
Wondering what color my screen had cleared, I added some more code and
found that the screen was cleared with value 0xFF.
Pondering on this one, I made the assumption that the DMA is NOT receiving
data from itself, but from the bus! Since there are no I/O cards to send data
down the bus, I assumed that 0xFF was a default value.
But then again, maybe DMA channel 0 wasn't working right. I took the lines
of code to initialize DMA channel 0 and the code to start the DMA transfer for
channel 0 out of the code and reran the test code. Much to my suprise, the
screen cleared twice as fast as before.
As for timing, my results aren't too accurate. In fact, don't even take
these as being true. The first test (with both DMA 0 and 1 enabled), cranked
out around 8.03 frames per second on my 486DX-33 VLB Cirrus Logic. The second
test (with just DMA 1 enabled), cranked out 18.23 fps.
This is about as far as I've gotten with memory to memory DMA transfers.
I'm going to be trying other DMA channels, and maybe even the 16-bit ones
to get a faster dump.
If anyone can contribute any information, please let me know. You will
be credited for any little tiny piece of help you can give. Maybe if we all
pull together, we might actually be able to do frame dumps in the background
while we're rendering our next frame... could prove to be useful!
You can reach me at 'nstalker@iag.net'.
- Breakpoint

View File

@@ -0,0 +1,7 @@
<html>
<head>
<meta http-equiv="refresh" content="0;url=/Linux.old/sabre/os/articles">
</head>
<body lang="zh-CN">
</body>
</html>