2 JPC-RR: A x86 PC Hardware Emulator
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2010 H. Ilari Liusvaara
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as published by
10 the Free Software Foundation.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 Based on JPC x86 PC Hardware emulator,
22 A project from the Physics Dept, The University of Oxford
24 Details about original JPC can be found at:
26 www-jpc.physics.ox.ac.uk
30 package org
.jpc
.emulator
.motherboard
;
32 import org
.jpc
.emulator
.*;
33 import org
.jpc
.emulator
.memory
.*;
37 * Emulation of an 8237 Direct Memory Access Controller
38 * @see <a href="http://pdos.csail.mit.edu/6.828/2007/readings/hardware/8237A.pdf">
39 * 8237A - Datasheet</a>
40 * @author Chris Dennis
42 public class DMAController
extends AbstractHardwareComponent
implements IOPortCapable
44 private static final int pagePortList0
= 0x1;
45 private static final int pagePortList1
= 0x2;
46 private static final int pagePortList2
= 0x3;
47 private static final int pagePortList3
= 0x7;
48 private static final int[] pagePortList
= new int[]{pagePortList0
,
49 pagePortList1
, pagePortList2
, pagePortList3
};
50 private static final int COMMAND_MEMORY_TO_MEMORY
= 0x01;
51 private static final int COMMAND_ADDRESS_HOLD
= 0x02;
52 private static final int COMMAND_COMPRESSED_TIMING
= 0x08;
53 private static final int COMMAND_CYCLIC_PRIORITY
= 0x10;
54 private static final int COMMAND_EXTENDED_WRITE
= 0x20;
55 private static final int COMMAND_DREQ_SENSE_LOW
= 0x40;
56 private static final int COMMAND_DACK_SENSE_LOW
= 0x80;
57 private static final int CMD_NOT_SUPPORTED
= COMMAND_MEMORY_TO_MEMORY
| COMMAND_ADDRESS_HOLD
| COMMAND_COMPRESSED_TIMING
| COMMAND_CYCLIC_PRIORITY
| COMMAND_EXTENDED_WRITE
| COMMAND_DREQ_SENSE_LOW
| COMMAND_DACK_SENSE_LOW
;
58 private static final int ADDRESS_READ_STATUS
= 0x8;
59 private static final int ADDRESS_READ_MASK
= 0xf;
60 private static final int ADDRESS_WRITE_COMMAND
= 0x8;
61 private static final int ADDRESS_WRITE_REQUEST
= 0x9;
62 private static final int ADDRESS_WRITE_MASK_BIT
= 0xa;
63 private static final int ADDRESS_WRITE_MODE
= 0xb;
64 private static final int ADDRESS_WRITE_FLIPFLOP
= 0xc;
65 private static final int ADDRESS_WRITE_CLEAR
= 0xd;
66 private static final int ADDRESS_WRITE_CLEAR_MASK
= 0xe;
67 private static final int ADDRESS_WRITE_MASK
= 0xf;
71 private boolean flipFlop
;
73 private int ioBase
, pageLowBase
, pageHighBase
;
74 private int controllerNumber
;
75 private PhysicalAddressSpace memory
;
76 private DMAChannel
[] dmaChannels
;
78 public static class DMAChannel
implements SRDumpable
80 private static final int MODE_CHANNEL_SELECT
= 0x03;
81 private static final int MODE_ADDRESS_INCREMENT
= 0x20;
82 private static final int MODE_AUTO_REINITIALIZE
= 0x10;
83 public static final int ADDRESS
= 0;
84 public static final int COUNT
= 1;
85 public int currentAddress
, currentWordCount
;
86 public int baseAddress
, baseWordCount
;
89 public DMATransferCapable transferDevice
;
90 public int pageLow
, pageHigh
;
91 private DMAController upperBackref
;
93 public void dumpSRPartial(SRDumper output
) throws IOException
95 output
.dumpInt(currentAddress
);
96 output
.dumpInt(currentWordCount
);
97 output
.dumpInt(baseAddress
);
98 output
.dumpInt(baseWordCount
);
100 output
.dumpInt(pageLow
);
101 output
.dumpInt(pageHigh
);
102 output
.dumpInt(dack
);
104 output
.dumpObject(transferDevice
);
105 output
.dumpObject(upperBackref
);
108 public DMAChannel(DMAController backref
)
110 upperBackref
= backref
;
113 public DMAChannel(SRLoader input
) throws IOException
115 input
.objectCreated(this);
116 currentAddress
= input
.loadInt();
117 currentWordCount
= input
.loadInt();
118 baseAddress
= input
.loadInt();
119 baseWordCount
= input
.loadInt();
120 mode
= input
.loadInt();
121 pageLow
= input
.loadInt();
122 pageHigh
= input
.loadInt();
123 dack
= input
.loadInt();
124 eop
= input
.loadInt();
125 transferDevice
= (DMATransferCapable
)input
.loadObject();
126 upperBackref
= (DMAController
)input
.loadObject();
130 public void dumpStatusPartial(StatusDumper output
)
132 //super.dumpStatusPartial(output); <no superclass, 20090704>
133 output
.println("\tupperBackref <object #" + output
.objectNumber(upperBackref
) + ">"); if(upperBackref
!= null) upperBackref
.dumpStatus(output
);
134 output
.println("\tcurrentAddress " + currentAddress
+ " currentWordCount " + currentWordCount
);
135 output
.println("\tbaseAddress " + baseAddress
+ " baseWordCount " + baseWordCount
);
136 output
.println("\tmode " + mode
+ " pageLow " + pageLow
+ " pageHigh " + pageHigh
);
137 output
.println("\tdack " + dack
+ " eop " + eop
);
138 output
.println("\ttransferDevice <object #" + output
.objectNumber(transferDevice
) + ">"); if(transferDevice
!= null) transferDevice
.dumpStatus(output
);
141 public void dumpStatus(StatusDumper output
)
143 if(output
.dumped(this))
146 output
.println("#" + output
.objectNumber(this) + ": DMAChannel:");
147 dumpStatusPartial(output
);
152 * Reads memory from this channel.
154 * Allows a <code>DMATransferCapable</code> device to read the section of
155 * memory currently pointed to by this channels internal registers.
156 * @param buffer byte[] to save data in.
157 * @param offset offset into <code>buffer</code>.
158 * @param position offset into channel's memory.
159 * @param length number of bytes to read.
161 public void readMemory(byte[] buffer
, int offset
, int position
, int length
)
163 int address
= (pageHigh
<< 24) | (pageLow
<< 16) | currentAddress
;
165 if ((mode
& DMAChannel
.MODE_ADDRESS_INCREMENT
) != 0) {
166 System
.err
.println("Warning: DMA read in address decrement mode");
167 //This may be broken for 16bit DMA
168 upperBackref
.memory
.copyContentsIntoArray(address
- position
- length
, buffer
, offset
, length
);
169 //Should have really decremented address with each byte read, so instead just reverse array order
170 for (int left
= offset
, right
= offset
+ length
- 1; left
< right
; left
++, right
--) {
171 byte temp
= buffer
[left
];
172 buffer
[left
] = buffer
[right
];
173 buffer
[right
] = temp
; // exchange the first and last
176 upperBackref
.memory
.copyContentsIntoArray(address
+ position
, buffer
, offset
, length
);
180 * Writes data to this channel.
182 * Allows a <code>DMATransferCapable</code> device to write to the section of
183 * memory currently pointed to by this channels internal registers.
184 * @param buffer byte[] containing data.
185 * @param offset offset into <code>buffer</code>.
186 * @param position offset into channel's memory.
187 * @param length number of bytes to write.
189 public void writeMemory(byte[] buffer
, int offset
, int position
, int length
)
191 int address
= (pageHigh
<< 24) | (pageLow
<< 16) | currentAddress
;
193 if ((mode
& DMAChannel
.MODE_ADDRESS_INCREMENT
) != 0) {
194 System
.err
.println("Warning: DMA write in address decrement mode");
195 //This may be broken for 16bit DMA
196 //Should really decremented address with each byte write, so instead we reverse the array order now
197 for (int left
= offset
, right
= offset
+ length
- 1; left
< right
; left
++, right
--) {
198 byte temp
= buffer
[left
];
199 buffer
[left
] = buffer
[right
];
200 buffer
[right
] = temp
; // exchange the first and last
202 upperBackref
.memory
.copyArrayIntoContents(address
- position
- length
, buffer
, offset
, length
);
204 upperBackref
.memory
.copyArrayIntoContents(address
+ position
, buffer
, offset
, length
);
207 private boolean transferComplete()
209 return (currentWordCount
>= ((baseWordCount
+ 1) << upperBackref
.controllerNumber
));
214 //Wrap auto-reinitialized DMA, don't offer single-block if not available.
215 if(transferComplete()) {
216 if((mode
& DMAChannel
.MODE_AUTO_REINITIALIZE
) != 0)
217 currentWordCount
= 0;
222 int n
= transferDevice
.handleTransfer(this, currentWordCount
, (baseWordCount
+ 1) << upperBackref
.controllerNumber
);
223 currentWordCount
= n
;
224 /* Try to wrap the buffer to get the auto-initialization effect. */
225 if(transferComplete() && (mode
& DMAChannel
.MODE_AUTO_REINITIALIZE
) != 0)
226 currentWordCount
= 0;
231 transferDevice
= null;
232 currentAddress
= currentWordCount
= mode
= 0;
233 baseAddress
= baseWordCount
= 0;
234 pageLow
= pageHigh
= dack
= eop
= 0;
239 * Constructs a DMA controller (either primary or secondary). If
240 * <code>highPageEnable</code> is true then 32 bit addressing is possible,
241 * otherwise the controller is limited to 24 bits.
242 * @param highPageEnable <code>true</code> if 32bit addressing required.
243 * @param primary <code>true</code> if primary controller.
245 public DMAController(boolean highPageEnable
, boolean primary
)
247 ioportRegistered
= false;
248 dShift
= primary ?
0 : 1;
249 ioBase
= primary ?
0x00 : 0xc0;
250 pageLowBase
= primary ?
0x80 : 0x88;
251 pageHighBase
= highPageEnable ?
(primary ?
0x480 : 0x488) : -1;
252 controllerNumber
= primary ?
0 : 1;
253 dmaChannels
= new DMAChannel
[4];
254 for (int i
= 0; i
< 4; i
++)
255 dmaChannels
[i
] = new DMAChannel(this);
259 public void dumpStatusPartial(StatusDumper output
)
261 super.dumpStatusPartial(output
);
262 output
.println("\tstatus " + status
+ " command " + command
+ " mask " + mask
+ " flipFlop " + flipFlop
);
263 output
.println("\tdShift " + dShift
+ " ioBase " + ioBase
+ " pageLowBase " + pageLowBase
);
264 output
.println("\tpageHighBase " + pageHighBase
+ " controllerNumber " + controllerNumber
);
265 output
.println("\tioportRegistered " + ioportRegistered
);
266 output
.println("\tmemory <object #" + output
.objectNumber(memory
) + ">"); if(memory
!= null) memory
.dumpStatus(output
);
267 for (int i
=0; i
< dmaChannels
.length
; i
++) {
268 output
.println("\tdmaChannels[" + i
+ "] <object #" + output
.objectNumber(dmaChannels
[i
]) + ">"); if(dmaChannels
[i
] != null) dmaChannels
[i
].dumpStatus(output
);
272 public void dumpStatus(StatusDumper output
)
274 if(output
.dumped(this))
277 output
.println("#" + output
.objectNumber(this) + ": DMAController:");
278 dumpStatusPartial(output
);
282 public void dumpSRPartial(SRDumper output
) throws IOException
284 super.dumpSRPartial(output
);
285 output
.dumpInt(status
);
286 output
.dumpInt(command
);
287 output
.dumpInt(mask
);
288 output
.dumpBoolean(flipFlop
);
289 output
.dumpInt(dShift
);
290 output
.dumpInt(ioBase
);
291 output
.dumpInt(pageLowBase
);
292 output
.dumpInt(pageHighBase
);
293 output
.dumpInt(controllerNumber
);
294 output
.dumpObject(memory
);
295 output
.dumpInt(dmaChannels
.length
);
296 for(int i
= 0; i
< dmaChannels
.length
; i
++)
297 output
.dumpObject(dmaChannels
[i
]);
298 output
.dumpBoolean(ioportRegistered
);
301 public DMAController(SRLoader input
) throws IOException
304 status
= input
.loadInt();
305 command
= input
.loadInt();
306 mask
= input
.loadInt();
307 flipFlop
= input
.loadBoolean();
308 dShift
= input
.loadInt();
309 ioBase
= input
.loadInt();
310 pageLowBase
= input
.loadInt();
311 pageHighBase
= input
.loadInt();
312 controllerNumber
= input
.loadInt();
313 memory
= (PhysicalAddressSpace
)input
.loadObject();
314 dmaChannels
= new DMAChannel
[input
.loadInt()];
315 for(int i
= 0; i
< dmaChannels
.length
; i
++)
316 dmaChannels
[i
] = (DMAChannel
)input
.loadObject();
317 ioportRegistered
= false;
318 if(input
.objectEndsHere())
320 ioportRegistered
= input
.loadBoolean();
324 * Returns true if this controller is the primary DMA controller.
326 * Non-primary or secondary controllers operate by being chained off the
327 * primary controller.
328 * @return <code>true</code> if this is the primary DMA controller.
330 public boolean isPrimary()
332 return (this.dShift
== 0);
337 for (int i
= 0; i
< dmaChannels
.length
; i
++)
338 dmaChannels
[i
].reset();
340 this.writeController(0x0d << this.dShift
, 0);
343 ioportRegistered
= false;
346 private void writeChannel(int portNumber
, int data
)
348 int port
= (portNumber
>>> dShift
) & 0x0f;
349 int channelNumber
= port
>>> 1;
350 DMAChannel r
= dmaChannels
[channelNumber
];
352 if ((port
& 1) == DMAChannel
.ADDRESS
)
353 r
.baseAddress
= (r
.baseAddress
& 0xff) | ((data
<< 8) & 0xff00);
355 r
.baseWordCount
= (r
.baseWordCount
& 0xff) | ((data
<< 8) & 0xff00);
356 initChannel(channelNumber
);
357 } else if ((port
& 1) == DMAChannel
.ADDRESS
)
358 r
.baseAddress
= (r
.baseAddress
& 0xff00) | (data
& 0xff);
360 r
.baseWordCount
= (r
.baseWordCount
& 0xff00) | (data
& 0xff);
363 private void writeController(int portNumber
, int data
)
367 int port
= (portNumber
>>> this.dShift
) & 0x0f;
369 case ADDRESS_WRITE_COMMAND
: /* command */
370 if ((data
!= 0) && ((data
& CMD_NOT_SUPPORTED
) != 0)) {
371 System
.err
.println("Warning: DMA: Command bits " + (data
& CMD_NOT_SUPPORTED
) + " not supported.");
376 case ADDRESS_WRITE_REQUEST
:
377 int channelNumber
= data
& 3;
380 status &= ~(1 << (channelNumber + 4));
382 status |= 1 << (channelNumber + 4);
384 status
&= ~
(1 << channelNumber
);
387 case ADDRESS_WRITE_MASK_BIT
:
388 r
= dmaChannels
[data
& 3];
389 tmp
= ((1 + r
.baseWordCount
) << controllerNumber
- r
.currentWordCount
);
390 if ((data
& 0x4) != 0) {
391 mask
|= 1 << (data
& 3);
393 mask
&= ~
(1 << (data
& 3));
398 case ADDRESS_WRITE_MODE
:
399 channelNumber
= data
& DMAChannel
.MODE_CHANNEL_SELECT
;
400 dmaChannels
[channelNumber
].mode
= data
;
402 case ADDRESS_WRITE_FLIPFLOP
:
405 case ADDRESS_WRITE_CLEAR
:
411 case ADDRESS_WRITE_CLEAR_MASK
: /* clear mask for all channels */
415 case ADDRESS_WRITE_MASK
: /* write mask for all channels */
424 private static final int[] channels
= new int[]{-1, 2, 3, 1,
427 private void writePageLow(int portNumber
, int data
)
429 int channelNumber
= channels
[portNumber
& 7];
430 if (-1 == channelNumber
)
432 dmaChannels
[channelNumber
].pageLow
= 0xff & data
;
435 private void writePageHigh(int portNumber
, int data
)
437 int channelNumber
= channels
[portNumber
& 7];
438 if (-1 == channelNumber
)
440 dmaChannels
[channelNumber
].pageHigh
= 0x7f & data
;
443 private int readChannel(int portNumber
)
445 int port
= (portNumber
>>> dShift
) & 0x0f;
446 int channelNumber
= port
>>> 1;
447 int registerNumber
= port
& 1;
448 DMAChannel r
= dmaChannels
[channelNumber
];
450 int direction
= ((r
.mode
& DMAChannel
.MODE_ADDRESS_INCREMENT
) == 0) ?
1 : -1;
452 boolean flipflop
= getFlipFlop();
454 if (registerNumber
!= 0)
455 val
= (r
.baseWordCount
<< dShift
) - r
.currentWordCount
;
457 val
= r
.currentAddress
+ r
.currentWordCount
* direction
;
458 return (val
>>> (dShift
+ (flipflop ?
0x8 : 0x0))) & 0xff;
461 private int readController(int portNumber
)
464 int port
= (portNumber
>>> dShift
) & 0x0f;
466 case ADDRESS_READ_STATUS
:
471 case ADDRESS_READ_MASK
:
482 private int readPageLow(int portNumber
)
484 int channelNumber
= channels
[portNumber
& 7];
485 if (-1 == channelNumber
)
487 return dmaChannels
[channelNumber
].pageLow
;
490 private int readPageHigh(int portNumber
)
492 int channelNumber
= channels
[portNumber
& 7];
493 if (-1 == channelNumber
)
495 return dmaChannels
[channelNumber
].pageHigh
;
498 public void ioPortWriteByte(int address
, int data
)
500 switch ((address
- ioBase
) >>> dShift
) {
509 writeChannel(address
, data
);
519 writeController(address
, data
);
525 switch (address
- pageLowBase
) {
530 writePageLow(address
, data
);
536 switch (address
- pageHighBase
) {
541 writePageHigh(address
, data
);
549 public void ioPortWriteWord(int address
, int data
)
551 this.ioPortWriteByte(address
, data
);
552 this.ioPortWriteByte(address
+ 1, data
>>> 8);
555 public void ioPortWriteLong(int address
, int data
)
557 this.ioPortWriteWord(address
, data
);
558 this.ioPortWriteWord(address
+ 2, data
>>> 16);
561 public int ioPortReadByte(int address
)
563 switch ((address
- ioBase
) >>> dShift
) {
572 return readChannel(address
);
581 return readController(address
);
586 switch (address
- pageLowBase
) {
591 return readPageLow(address
);
596 switch (address
- pageHighBase
) {
601 return readPageHigh(address
);
609 public int ioPortReadWord(int address
)
611 return (0xff & this.ioPortReadByte(address
)) | ((this.ioPortReadByte(address
) << 8) & 0xff);
614 public int ioPortReadLong(int address
)
616 return (0xffff & this.ioPortReadByte(address
)) | ((this.ioPortReadByte(address
) << 16) & 0xffff);
619 public int[] ioPortsRequested()
622 if (pageHighBase
>= 0)
623 temp
= new int[16 + (2 * pagePortList
.length
)];
625 temp
= new int[16 + pagePortList
.length
];
628 for (int i
= 0; i
< 8; i
++)
629 temp
[j
++] = ioBase
+ (i
<< this.dShift
);
630 for (int i
= 0; i
< pagePortList
.length
; i
++) {
631 temp
[j
++] = pageLowBase
+ pagePortList
[i
];
632 if (pageHighBase
>= 0)
633 temp
[j
++] = pageHighBase
+ pagePortList
[i
];
636 for (int i
= 0; i
< 8; i
++)
637 temp
[j
++] = ioBase
+ ((i
+ 8) << this.dShift
);
641 private boolean getFlipFlop()
643 boolean ff
= flipFlop
;
648 private void initChannel(int channelNumber
)
650 DMAChannel r
= dmaChannels
[channelNumber
];
651 r
.currentAddress
= r
.baseAddress
<< dShift
;
652 r
.currentWordCount
= 0;
655 private static int numberOfTrailingZeros(int i
)
690 return n
- ((i
<< 1) >>> 31);
693 private void runTransfers()
695 int value
= ~mask
& (status
>>> 4) & 0xf;
700 int channel
= numberOfTrailingZeros(value
);
702 dmaChannels
[channel
].run();
705 value
&= ~
(1 << channel
);
710 * Returns the mode register of the given DMA channel.
711 * @param channel channel index.
712 * @return mode register value.
714 public int getChannelMode(int channel
)
716 return dmaChannels
[channel
].mode
;
720 * Request a DMA transfer operation to occur on the specified channel.
722 * This is equivalent to pulling the DREQ line high on the controller.
723 * @param channel channel index.
725 public void holdDmaRequest(int channel
)
727 status
|= 1 << (channel
+ 4);
732 * Request the DMA transfer in operation on the specified channel to stop.
734 * This is equivalent to pulling the DREQ line low on the controller.
735 * @param channel channel index.
737 public void releaseDmaRequest(int channel
)
739 status
&= ~
(1 << (channel
+ 4));
743 * Register the given <code>DMATransferCapable</code> device with the
746 * Subsequent DMA requests on this channel will call the
747 * <code>handleTransfer</code> method on <code>device</code>.
748 * @param channel channel index.
749 * @param device target of transfers.
751 public void registerChannel(int channel
, DMATransferCapable device
)
753 dmaChannels
[channel
].transferDevice
= device
;
755 private boolean ioportRegistered
;
757 public boolean initialised()
759 return ((memory
!= null) && ioportRegistered
);
762 public void acceptComponent(HardwareComponent component
)
764 if (component
instanceof PhysicalAddressSpace
)
765 this.memory
= (PhysicalAddressSpace
) component
;
766 if (component
instanceof IOPortHandler
) {
767 ((IOPortHandler
) component
).registerIOPortCapable(this);
768 ioportRegistered
= true;
773 public String
toString()
775 return "DMA Controller [element " + dShift
+ "]";