add directory study
This commit is contained in:
BIN
study/sabre/os/files/MiscHW/8237A_DMAControllerDatasheet.pdf
Normal file
BIN
study/sabre/os/files/MiscHW/8237A_DMAControllerDatasheet.pdf
Normal file
Binary file not shown.
3781
study/sabre/os/files/MiscHW/8259A_PIC_Datasheet.pdf
Normal file
3781
study/sabre/os/files/MiscHW/8259A_PIC_Datasheet.pdf
Normal file
File diff suppressed because it is too large
Load Diff
290
study/sabre/os/files/MiscHW/8259pic.txt
Normal file
290
study/sabre/os/files/MiscHW/8259pic.txt
Normal 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.
|
||||
|
||||
|
||||
197
study/sabre/os/files/MiscHW/8259pic2.txt
Normal file
197
study/sabre/os/files/MiscHW/8259pic2.txt
Normal 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.
|
||||
|
||||
BIN
study/sabre/os/files/MiscHW/ACPISpec1.0a.pdf
Normal file
BIN
study/sabre/os/files/MiscHW/ACPISpec1.0a.pdf
Normal file
Binary file not shown.
181
study/sabre/os/files/MiscHW/CMOSTimer.html
Normal file
181
study/sabre/os/files/MiscHW/CMOSTimer.html
Normal 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 © 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 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 was originally going to write an article for the
|
||||
ill-fated PCGPE 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 also
|
||||
initializes the PIT chip, which then periodically (ie 18.2
|
||||
times a second) generates an interrupt to update the time
|
||||
variables. Messing with the PIT 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 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 shell).</p>
|
||||
|
||||
<p><b>Ok, So How Do I 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) &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 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 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 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) 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) 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 "glitch" in animation" 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() 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 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 haven't actually tried using the CMOS timer for tripple
|
||||
buffering, but I 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 + 15) 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 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 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>
|
||||
BIN
study/sabre/os/files/MiscHW/CP_interupt.pdf
Normal file
BIN
study/sabre/os/files/MiscHW/CP_interupt.pdf
Normal file
Binary file not shown.
158
study/sabre/os/files/MiscHW/DMAPORTS.txt
Normal file
158
study/sabre/os/files/MiscHW/DMAPORTS.txt
Normal 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
|
||||
1003
study/sabre/os/files/MiscHW/DMA_API.txt
Normal file
1003
study/sabre/os/files/MiscHW/DMA_API.txt
Normal file
File diff suppressed because it is too large
Load Diff
312
study/sabre/os/files/MiscHW/DMA_CODE.asm
Normal file
312
study/sabre/os/files/MiscHW/DMA_CODE.asm
Normal 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
|
||||
382
study/sabre/os/files/MiscHW/DMA_RTI.txt
Normal file
382
study/sabre/os/files/MiscHW/DMA_RTI.txt
Normal 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
|
||||
506
study/sabre/os/files/MiscHW/DMA_VLA.txt
Normal file
506
study/sabre/os/files/MiscHW/DMA_VLA.txt
Normal 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
|
||||
291
study/sabre/os/files/MiscHW/PIT.txt
Normal file
291
study/sabre/os/files/MiscHW/PIT.txt
Normal 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
|
||||
263
study/sabre/os/files/MiscHW/RealTimeClock.txt
Normal file
263
study/sabre/os/files/MiscHW/RealTimeClock.txt
Normal 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.
|
||||
413
study/sabre/os/files/MiscHW/RealtimeClockFAQ.txt
Normal file
413
study/sabre/os/files/MiscHW/RealtimeClockFAQ.txt
Normal 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.
|
||||
857
study/sabre/os/files/MiscHW/TIMER.c
Normal file
857
study/sabre/os/files/MiscHW/TIMER.c
Normal 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__ */
|
||||
|
||||
192
study/sabre/os/files/MiscHW/TIMER2.txt
Normal file
192
study/sabre/os/files/MiscHW/TIMER2.txt
Normal 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
|
||||
706
study/sabre/os/files/MiscHW/dmaprog8237.txt
Normal file
706
study/sabre/os/files/MiscHW/dmaprog8237.txt
Normal 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, ®s, ®s, &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, ®s, ®s, &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
|
||||
|
||||
|
||||
7
study/sabre/os/files/MiscHW/index.html
Normal file
7
study/sabre/os/files/MiscHW/index.html
Normal 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>
|
||||
Reference in New Issue
Block a user