/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.system.x86;
import java.security.PrivilegedExceptionAction;
import javax.naming.NameNotFoundException;
import org.jnode.naming.InitialNaming;
import org.jnode.system.resource.DMAException;
import org.jnode.system.resource.IOResource;
import org.jnode.system.resource.ResourceManager;
import org.jnode.system.resource.ResourceNotFreeException;
import org.jnode.system.resource.ResourceOwner;
import org.jnode.system.resource.SimpleResourceOwner;
import org.jnode.util.AccessControllerUtils;
import org.jnode.annotation.MagicPermission;
import org.vmmagic.unboxed.Address;
/*
* NOTES about DMA transfers:
*
* controller 1: channels 0-3, byte operations, ports 00-1F
* controller 2: channels 4-7, word operations, ports C0-DF
*
* - ALL registers are 8 bits only, regardless of transfer size
* - channel 4 is not used - cascades 1 into 2.
* - channels 0-3 are byte - addresses/counts are for physical bytes
* - channels 5-7 are word - addresses/counts are for physical words
* - transfers must not cross physical 64K (0-3) or 128K (5-7) boundaries
* - transfer count loaded to registers is 1 less than actual count
* - controller 2 offsets are all even (2x offsets for controller 1)
* - page registers for 5-7 don't use data bit 0, represent 128K pages
* - page registers for 0-3 use bit 0, represent 64K pages
*
* DMA transfers are limited to the lower 16MB of _physical_ memory.
* Note that addresses loaded into registers must be _physical_ addresses,
* not logical addresses (which may differ if paging is active).
*
* Address mapping for channels 0-3:
*
* A23 ... A16 A15 ... A8 A7 ... A0 (Physical addresses)
* | ... | | ... | | ... |
* | ... | | ... | | ... |
* | ... | | ... | | ... |
* P7 ... P0 A7 ... A0 A7 ... A0
* | Page | Addr MSB | Addr LSB | (DMA registers)
*
* Address mapping for channels 5-7:
*
* A23 ... A17 A16 A15 ... A9 A8 A7 ... A1 A0 (Physical addresses)
* | ... | \ \ ... \ \ \ ... \ \
* | ... | \ \ ... \ \ \ ... \ (not used)
* | ... | \ \ ... \ \ \ ... \
* P7 ... P1 (0) A7 A6 ... A0 A7 A6 ... A0
* | Page | Addr MSB | Addr LSB | (DMA registers)
*
* Again, channels 5-7 transfer _physical_ words (16 bits), so addresses
* and counts _must_ be word-aligned (the lowest address bit is _ignored_ at
* the hardware level, so odd-byte transfers aren't possible).
*
* Transfer count (_not # bytes_) is limited to 64K, represented as actual
* count - 1 : 64K => 0xFFFF, 1 => 0x0000. Thus, count is always 1 or more,
* and up to 128K bytes may be transferred on channels 5-7 in one operation.
*
* TAKEN FROM Linux kernel.
*/
/**
* @author epr
*/
@MagicPermission
final class DMA implements DMAConstants {
/**
* Number of channels
*/
public static final int MAX = 8;
/**
* Page I/O ports
*/
private final IOResource pageIO;
private final IOResource dma1IO;
private final IOResource dma2IO;
/**
* Create a new instance
*
* @throws DMAException
*/
public DMA() throws DMAException {
final ResourceManager rm;
try {
rm = InitialNaming.lookup(ResourceManager.NAME);
} catch (NameNotFoundException ex) {
throw new DMAException("Cannot find ResourceManager", ex);
}
IOResource pageIO = null;
IOResource dma1IO = null;
IOResource dma2IO = null;
try {
final ResourceOwner owner = new SimpleResourceOwner("DMA-X86");
pageIO = claimPorts(rm, owner, 0x81, 0x8f - 0x81 + 1);
dma1IO = claimPorts(rm, owner, 0x00, 16);
dma2IO = claimPorts(rm, owner, 0xc0, 32);
this.pageIO = pageIO;
this.dma1IO = dma1IO;
this.dma2IO = dma2IO;
for (int dmanr = 0; dmanr < MAX; dmanr++) {
clearFF(dmanr);
}
} catch (ResourceNotFreeException ex) {
if (pageIO != null) {
pageIO.release();
}
if (dma1IO != null) {
dma1IO.release();
}
if (dma2IO != null) {
dma2IO.release();
}
throw new DMAException("Cannot claim DMA I/O ports", ex);
}
}
/**
* Release all resources
*/
protected final void release() {
pageIO.release();
dma1IO.release();
dma2IO.release();
}
/**
* Program the page register for a given channel
*
* @param dmanr
* @param page
*/
private final void setPage(int dmanr, int page) {
switch (dmanr) {
case 0:
pageIO.outPortByte(DMA_PAGE_0, page);
break;
case 1:
pageIO.outPortByte(DMA_PAGE_1, page);
break;
case 2:
pageIO.outPortByte(DMA_PAGE_2, page);
break;
case 3:
pageIO.outPortByte(DMA_PAGE_3, page);
break;
case 5:
pageIO.outPortByte(DMA_PAGE_5, page & 0xfe);
break;
case 6:
pageIO.outPortByte(DMA_PAGE_6, page & 0xfe);
break;
case 7:
pageIO.outPortByte(DMA_PAGE_7, page & 0xfe);
break;
default:
throw new IllegalArgumentException("Invalid dmanr " + dmanr);
}
}
/**
* Program the address register for a given channel
*
* @param dmanr
* @param address
* @throws DMAException
*/
public void setAddress(int dmanr, Address address) throws DMAException {
final int a32 = address.toInt();
final int page = (a32 >> 16);
setPage(dmanr, page);
if (dmanr <= 3) {
final int port = DMA_ADDR_0 + ((dmanr & 3) << 1);
dma1IO.outPortByte(port, a32 & 0xFF);
dma1IO.outPortByte(port, (a32 >> 8) & 0xFF);
} else {
final int port = DMA_ADDR_4 + ((dmanr & 3) << 2);
dma1IO.outPortByte(port, (a32 >> 1) & 0xFF);
dma1IO.outPortByte(port, (a32 >> 9) & 0xFF);
}
}
/**
* Program the address register for a given channel
*
* @param dmanr
* @param length
* @throws DMAException
*/
public void setLength(int dmanr, int length) throws DMAException {
length--;
if (dmanr <= 3) {
final int port = DMA_CNT_0 + ((dmanr & 3) << 1);
dma1IO.outPortByte(port, length & 0xFF);
dma1IO.outPortByte(port, (length >> 8) & 0xFF);
} else {
final int port = DMA_CNT_4 + ((dmanr & 3) << 2);
dma1IO.outPortByte(port, (length >> 1) & 0xFF);
dma1IO.outPortByte(port, (length >> 9) & 0xFF);
}
}
public int getLength(int dmanr) {
final int port;
int count;
if (dmanr <= 3) {
port = DMA_CNT_0 + ((dmanr & 3) << 1);
} else {
port = DMA_CNT_4 + ((dmanr & 3) << 2);
}
count = (dma1IO.inPortByte(port) & 0xFF) + 1;
count += ((dma1IO.inPortByte(port) & 0xFF) << 8);
if (dmanr <= 3) {
return count;
} else {
return count << 1;
}
}
/**
* Program the mode register for a given channel
*
* @param dmanr
* @param mode
* @throws DMAException
*/
public void setMode(int dmanr, int mode) throws DMAException {
mode |= (dmanr & 3);
if (dmanr <= 3) {
dma1IO.outPortByte(DMA1_MODE_REG, mode);
} else {
dma2IO.outPortByte(DMA2_MODE_REG, mode);
}
}
/**
* Enable the given channel
*
* @param dmanr
*/
public void enable(int dmanr) {
if (dmanr <= 3) {
dma1IO.outPortByte(DMA1_MASK_REG, dmanr);
} else {
dma2IO.outPortByte(DMA2_MASK_REG, dmanr & 3);
}
}
/**
* Disable the given channel
*
* @param dmanr
*/
public void disable(int dmanr) {
if (dmanr <= 3) {
dma1IO.outPortByte(DMA1_MASK_REG, dmanr | 4);
} else {
dma2IO.outPortByte(DMA2_MASK_REG, (dmanr & 3) | 4);
}
}
/**
* Clear the 'DMA Pointer Flip Flop'.
* Write 0 for LSB/MSB, 1 for MSB/LSB access.
* Use this once to initialize the FF to a known state.
* After that, keep track of it. :-)
*
* @param dmanr
*/
protected final void clearFF(int dmanr) {
if (dmanr <= 3) {
dma1IO.outPortByte(DMA1_CLEAR_FF_REG, 0);
} else {
dma2IO.outPortByte(DMA2_CLEAR_FF_REG, 0);
}
}
/**
* Test the combination of address and length
*
* @param dmanr
* @param address
* @param length
* @throws IllegalArgumentException
*/
protected final void test(int dmanr, Address address, int length)
throws IllegalArgumentException {
final int maxLength;
final int pageMask;
if (dmanr <= 3) {
maxLength = 64 * 1024;
pageMask = 0xff;
} else {
maxLength = 128 * 1024;
if ((length & 2) != 0) {
throw new IllegalArgumentException("Invalid length-alignment: " + length);
}
pageMask = 0xfe;
}
if ((length <= 0) || (length > maxLength)) {
throw new IllegalArgumentException("Invalid length: " + length);
}
final int a32 = address.toInt();
final int pageStart = (a32 >> 16) & pageMask;
final int pageEnd = ((a32 + length - 1) >> 16) & pageMask;
if (pageStart != pageEnd) {
throw new IllegalArgumentException("Invalid address alignment. DMA block cannot cross pages");
}
}
private IOResource claimPorts(final ResourceManager rm, final ResourceOwner owner, final int low, final int length)
throws ResourceNotFreeException, DMAException {
try {
return AccessControllerUtils.doPrivileged(new PrivilegedExceptionAction<IOResource>() {
public IOResource run() throws ResourceNotFreeException {
return rm.claimIOResource(owner, low, length);
}
});
} catch (ResourceNotFreeException ex) {
throw ex;
} catch (Exception ex) {
throw new DMAException("Unknown exception", ex);
}
}
}