Sound card support
[jpcrr.git] / org / jpc / emulator / motherboard / DMAController.java
blob9195c4312d30cbb55956101a88fbfe495dd4c50d
1 /*
2 JPC-RR: A x86 PC Hardware Emulator
3 Release 1
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.*;
34 import java.io.*;
36 /**
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;
68 private int status;
69 private int command;
70 private int mask;
71 private boolean flipFlop;
72 private int dShift;
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;
87 public int mode;
88 public int dack, eop;
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);
99 output.dumpInt(mode);
100 output.dumpInt(pageLow);
101 output.dumpInt(pageHigh);
102 output.dumpInt(dack);
103 output.dumpInt(eop);
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))
144 return;
146 output.println("#" + output.objectNumber(this) + ": DMAChannel:");
147 dumpStatusPartial(output);
148 output.endObject();
152 * Reads memory from this channel.
153 * <p>
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
175 } else
176 upperBackref.memory.copyContentsIntoArray(address + position, buffer, offset, length);
180 * Writes data to this channel.
181 * <p>
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);
203 } else
204 upperBackref.memory.copyArrayIntoContents(address + position, buffer, offset, length);
207 private boolean transferComplete()
209 return (currentWordCount >= ((baseWordCount + 1) << upperBackref.controllerNumber));
212 private void run()
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;
218 else
219 return;
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;
229 public void reset()
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);
256 reset();
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))
275 return;
277 output.println("#" + output.objectNumber(this) + ": DMAController:");
278 dumpStatusPartial(output);
279 output.endObject();
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
303 super(input);
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())
319 return;
320 ioportRegistered = input.loadBoolean();
324 * Returns true if this controller is the primary DMA controller.
325 * <p>
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);
335 public void reset()
337 for (int i = 0; i < dmaChannels.length; i++)
338 dmaChannels[i].reset();
340 this.writeController(0x0d << this.dShift, 0);
342 memory = null;
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];
351 if (getFlipFlop()) {
352 if ((port & 1) == DMAChannel.ADDRESS)
353 r.baseAddress = (r.baseAddress & 0xff) | ((data << 8) & 0xff00);
354 else
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);
359 else
360 r.baseWordCount = (r.baseWordCount & 0xff00) | (data & 0xff);
363 private void writeController(int portNumber, int data)
365 DMAChannel r;
366 int tmp;
367 int port = (portNumber >>> this.dShift) & 0x0f;
368 switch (port) {
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.");
372 break;
374 command = data;
375 break;
376 case ADDRESS_WRITE_REQUEST:
377 int channelNumber = data & 3;
379 if ((data & 4) == 0)
380 status &= ~(1 << (channelNumber + 4));
381 else
382 status |= 1 << (channelNumber + 4);
384 status &= ~(1 << channelNumber);
385 runTransfers();
386 break;
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);
392 } else {
393 mask &= ~(1 << (data & 3));
394 runTransfers();
397 break;
398 case ADDRESS_WRITE_MODE:
399 channelNumber = data & DMAChannel.MODE_CHANNEL_SELECT;
400 dmaChannels[channelNumber].mode = data;
401 break;
402 case ADDRESS_WRITE_FLIPFLOP:
403 flipFlop = false;
404 break;
405 case ADDRESS_WRITE_CLEAR:
406 flipFlop = false;
407 mask = ~0;
408 status &= 0xF0;
409 command = 0;
410 break;
411 case ADDRESS_WRITE_CLEAR_MASK: /* clear mask for all channels */
412 mask = 0;
413 runTransfers();
414 break;
415 case ADDRESS_WRITE_MASK: /* write mask for all channels */
416 mask = data;
417 runTransfers();
418 break;
419 default:
420 break;
424 private static final int[] channels = new int[]{-1, 2, 3, 1,
425 -1, -1, -1, 0};
427 private void writePageLow(int portNumber, int data)
429 int channelNumber = channels[portNumber & 7];
430 if (-1 == channelNumber)
431 return;
432 dmaChannels[channelNumber].pageLow = 0xff & data;
435 private void writePageHigh(int portNumber, int data)
437 int channelNumber = channels[portNumber & 7];
438 if (-1 == channelNumber)
439 return;
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();
453 int val;
454 if (registerNumber != 0)
455 val = (r.baseWordCount << dShift) - r.currentWordCount;
456 else
457 val = r.currentAddress + r.currentWordCount * direction;
458 return (val >>> (dShift + (flipflop ? 0x8 : 0x0))) & 0xff;
461 private int readController(int portNumber)
463 int val;
464 int port = (portNumber >>> dShift) & 0x0f;
465 switch (port) {
466 case ADDRESS_READ_STATUS:
467 val = status;
468 status &=
469 0xf0;
470 break;
471 case ADDRESS_READ_MASK:
472 val = mask;
473 break;
474 default:
475 val = 0;
476 break;
479 return val;
482 private int readPageLow(int portNumber)
484 int channelNumber = channels[portNumber & 7];
485 if (-1 == channelNumber)
486 return 0;
487 return dmaChannels[channelNumber].pageLow;
490 private int readPageHigh(int portNumber)
492 int channelNumber = channels[portNumber & 7];
493 if (-1 == channelNumber)
494 return 0;
495 return dmaChannels[channelNumber].pageHigh;
498 public void ioPortWriteByte(int address, int data)
500 switch ((address - ioBase) >>> dShift) {
501 case 0x0:
502 case 0x1:
503 case 0x2:
504 case 0x3:
505 case 0x4:
506 case 0x5:
507 case 0x6:
508 case 0x7:
509 writeChannel(address, data);
510 return;
511 case 0x8:
512 case 0x9:
513 case 0xa:
514 case 0xb:
515 case 0xc:
516 case 0xd:
517 case 0xe:
518 case 0xf:
519 writeController(address, data);
520 return;
521 default:
522 break;
525 switch (address - pageLowBase) {
526 case pagePortList0:
527 case pagePortList1:
528 case pagePortList2:
529 case pagePortList3:
530 writePageLow(address, data);
531 return;
532 default:
533 break;
536 switch (address - pageHighBase) {
537 case pagePortList0:
538 case pagePortList1:
539 case pagePortList2:
540 case pagePortList3:
541 writePageHigh(address, data);
542 return;
543 default:
544 break;
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) {
564 case 0x0:
565 case 0x1:
566 case 0x2:
567 case 0x3:
568 case 0x4:
569 case 0x5:
570 case 0x6:
571 case 0x7:
572 return readChannel(address);
573 case 0x8:
574 case 0x9:
575 case 0xa:
576 case 0xb:
577 case 0xc:
578 case 0xd:
579 case 0xe:
580 case 0xf:
581 return readController(address);
582 default:
583 break;
586 switch (address - pageLowBase) {
587 case pagePortList0:
588 case pagePortList1:
589 case pagePortList2:
590 case pagePortList3:
591 return readPageLow(address);
592 default:
593 break;
596 switch (address - pageHighBase) {
597 case pagePortList0:
598 case pagePortList1:
599 case pagePortList2:
600 case pagePortList3:
601 return readPageHigh(address);
602 default:
603 break;
606 return 0xff;
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()
621 int[] temp;
622 if (pageHighBase >= 0)
623 temp = new int[16 + (2 * pagePortList.length)];
624 else
625 temp = new int[16 + pagePortList.length];
627 int j = 0;
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);
638 return temp;
641 private boolean getFlipFlop()
643 boolean ff = flipFlop;
644 flipFlop = !ff;
645 return ff;
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)
657 int y;
658 if (i == 0)
659 return 32;
660 int n = 31;
662 y = i << 16;
663 if (y != 0)
665 n = n - 16;
666 i = y;
669 y = i << 8;
670 if (y != 0)
672 n = n - 8;
673 i = y;
676 y = i << 4;
677 if (y != 0)
679 n = n - 4;
680 i = y;
683 y = i << 2;
684 if (y != 0)
686 n = n - 2;
687 i = y;
690 return n - ((i << 1) >>> 31);
693 private void runTransfers()
695 int value = ~mask & (status >>> 4) & 0xf;
696 if (value == 0)
697 return;
699 while (value != 0) {
700 int channel = numberOfTrailingZeros(value);
701 if (channel < 4) {
702 dmaChannels[channel].run();
703 } else
704 break;
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.
721 * <p>
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);
728 runTransfers();
732 * Request the DMA transfer in operation on the specified channel to stop.
733 * <p>
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
744 * specified channel.
745 * <p>
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 + "]";