/* 3b2_dmac.c: AT&T 3B2 Model 400 AM9517A DMA Controller Implementation | |
Copyright (c) 2017, Seth J. Morabito | |
Permission is hereby granted, free of charge, to any person | |
obtaining a copy of this software and associated documentation | |
files (the "Software"), to deal in the Software without | |
restriction, including without limitation the rights to use, copy, | |
modify, merge, publish, distribute, sublicense, and/or sell copies | |
of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be | |
included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
Except as contained in this notice, the name of the author shall | |
not be used in advertising or otherwise to promote the sale, use or | |
other dealings in this Software without prior written authorization | |
from the author. | |
*/ | |
#include "3b2_dmac.h" | |
DMA_STATE dma_state; | |
UNIT dmac_unit[] = { | |
{ UDATA (NULL, 0, 0), 0, 0 }, | |
{ UDATA (NULL, 0, 0), 0, 1 }, | |
{ UDATA (NULL, 0, 0), 0, 2 }, | |
{ UDATA (NULL, 0, 0), 0, 3 }, | |
{ NULL } | |
}; | |
REG dmac_reg[] = { | |
{ NULL } | |
}; | |
DEVICE dmac_dev = { | |
"DMAC", dmac_unit, dmac_reg, NULL, | |
1, 16, 8, 4, 16, 32, | |
NULL, NULL, &dmac_reset, | |
NULL, NULL, NULL, NULL, | |
DEV_DEBUG, 0, sys_deb_tab | |
}; | |
dmac_drq_handler dmac_drq_handlers[] = { | |
{DMA_ID_CHAN, IDBASE+ID_DATA_REG, &id_drq, id_drq_handled}, | |
{DMA_IF_CHAN, IFBASE+IF_DATA_REG, &if_state.drq, if_drq_handled}, | |
{DMA_IUA_CHAN, IUBASE+IUA_DATA_REG, &iu_state.drqa, iua_drq_handled}, | |
{DMA_IUB_CHAN, IUBASE+IUB_DATA_REG, &iu_state.drqb, iub_drq_handled}, | |
{0, 0, NULL, NULL } | |
}; | |
t_stat dmac_reset(DEVICE *dptr) | |
{ | |
int i; | |
memset(&dma_state, 0, sizeof(dma_state)); | |
for (i = 0; i < 4; i++) { | |
dma_state.channels[i].page = 0; | |
dma_state.channels[i].addr = 0; | |
dma_state.channels[i].wcount = 0; | |
dma_state.channels[i].addr_c = 0; | |
dma_state.channels[i].wcount_c = 0; | |
} | |
return SCPE_OK; | |
} | |
uint32 dmac_read(uint32 pa, size_t size) | |
{ | |
uint8 reg, base, data; | |
base =(uint8) (pa >> 12); | |
reg = pa & 0xff; | |
switch (base) { | |
case DMA_C: /* 0x48xxx */ | |
switch (reg) { | |
case 0: /* channel 0 current address reg */ | |
data = ((dma_state.channels[0].addr_c) >> (dma_state.bff * 8)) & 0xff; | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] Reading Channel 0 Addr Reg: %08x\n", | |
R[NUM_PC], data); | |
dma_state.bff ^= 1; | |
break; | |
case 1: /* channel 0 current address reg */ | |
data = ((dma_state.channels[0].wcount_c) >> (dma_state.bff * 8)) & 0xff; | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] Reading Channel 0 Addr Count Reg: %08x\n", | |
R[NUM_PC], data); | |
dma_state.bff ^= 1; | |
break; | |
case 2: /* channel 1 current address reg */ | |
data = ((dma_state.channels[1].addr_c) >> (dma_state.bff * 8)) & 0xff; | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] Reading Channel 1 Addr Reg: %08x\n", | |
R[NUM_PC], data); | |
dma_state.bff ^= 1; | |
break; | |
case 3: /* channel 1 current address reg */ | |
data = ((dma_state.channels[1].wcount_c) >> (dma_state.bff * 8)) & 0xff; | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] Reading Channel 1 Addr Count Reg: %08x\n", | |
R[NUM_PC], data); | |
dma_state.bff ^= 1; | |
break; | |
case 4: /* channel 2 current address reg */ | |
data = ((dma_state.channels[2].addr_c) >> (dma_state.bff * 8)) & 0xff; | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] Reading Channel 2 Addr Reg: %08x\n", | |
R[NUM_PC], data); | |
dma_state.bff ^= 1; | |
break; | |
case 5: /* channel 2 current address reg */ | |
data = ((dma_state.channels[2].wcount_c) >> (dma_state.bff * 8)) & 0xff; | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] Reading Channel 2 Addr Count Reg: %08x\n", | |
R[NUM_PC], data); | |
dma_state.bff ^= 1; | |
break; | |
case 6: /* channel 3 current address reg */ | |
data = ((dma_state.channels[3].addr_c) >> (dma_state.bff * 8)) & 0xff; | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] Reading Channel 3 Addr Reg: %08x\n", | |
R[NUM_PC], data); | |
dma_state.bff ^= 1; | |
break; | |
case 7: /* channel 3 current address reg */ | |
data = ((dma_state.channels[3].wcount_c) >> (dma_state.bff * 8)) & 0xff; | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] Reading Channel 3 Addr Count Reg: %08x\n", | |
R[NUM_PC], data); | |
dma_state.bff ^= 1; | |
break; | |
case 8: | |
data = dma_state.status; | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] Reading DMAC Status %08x\n", | |
R[NUM_PC], data); | |
dma_state.status = 0; | |
break; | |
default: | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] DMAC READ %lu B @ %08x\n", | |
R[NUM_PC], size, pa); | |
data = 0; | |
} | |
return data; | |
default: | |
sim_debug(READ_MSG, &dmac_dev, | |
"[%08x] [BASE: %08x] DMAC READ %lu B @ %08x\n", | |
R[NUM_PC], base, size, pa); | |
return 0; | |
} | |
} | |
/* | |
* Program the DMAC | |
*/ | |
void dmac_program(uint8 reg, uint8 val) | |
{ | |
uint8 channel_id, i, chan_num; | |
dma_channel *channel; | |
if (reg < 8) { | |
switch (reg) { | |
case 0: | |
case 1: | |
chan_num = 0; | |
break; | |
case 2: | |
case 3: | |
chan_num = 1; | |
break; | |
case 4: | |
case 5: | |
chan_num = 2; | |
break; | |
case 6: | |
case 7: | |
chan_num = 3; | |
break; | |
} | |
channel = &dma_state.channels[chan_num]; | |
if (channel == NULL) { | |
/* This should never happen */ | |
return; | |
} | |
switch (reg & 1) { | |
case 0: /* Address */ | |
channel->addr &= ~(0xff << dma_state.bff * 8); | |
channel->addr |= (val & 0xff) << (dma_state.bff * 8); | |
channel->addr_c = channel->addr; | |
sim_debug(WRITE_MSG, &dmac_dev, | |
"Set address channel %d byte %d = %08x\n", | |
chan_num, dma_state.bff, channel->addr); | |
break; | |
case 1: /* Word Count */ | |
channel->wcount &= ~(0xff << dma_state.bff * 8); | |
channel->wcount |= (val & 0xff) << (dma_state.bff * 8); | |
channel->wcount_c = channel->wcount; | |
sim_debug(WRITE_MSG, &dmac_dev, | |
"Set word count channel %d byte %d = %08x\n", | |
chan_num, dma_state.bff, channel->wcount); | |
break; | |
} | |
/* Toggle the byte flip-flop */ | |
dma_state.bff ^= 1; | |
/* Handled. */ | |
return; | |
} | |
/* If it hasn't been handled, it must be one of the following | |
registers. */ | |
switch (reg) { | |
case 8: /* Command */ | |
dma_state.command = val; | |
sim_debug(WRITE_MSG, &dmac_dev, | |
"[%08x] Command: val=%02x\n", | |
R[NUM_PC], val); | |
break; | |
case 9: /* Request */ | |
sim_debug(WRITE_MSG, &dmac_dev, | |
"[%08x] Request set: val=%02x\n", | |
R[NUM_PC], val); | |
dma_state.request = val; | |
break; | |
case 10: /* Write Single Mask Register Bit */ | |
channel_id = val & 3; | |
/* "Clear or Set" is bit 2 */ | |
if ((val >> 2) & 1) { | |
dma_state.mask |= (1 << channel_id); | |
} else { | |
dma_state.mask &= ~(1 << channel_id); | |
} | |
sim_debug(WRITE_MSG, &dmac_dev, | |
"[%08x] Write Single Mask Register Bit. channel=%d set/clear=%02x\n", | |
R[NUM_PC], channel_id, (val >> 2) & 1); | |
break; | |
case 11: /* Mode */ | |
sim_debug(WRITE_MSG, &dmac_dev, | |
"[%08x] Mode Set. val=%02x\n", | |
R[NUM_PC], val); | |
dma_state.mode = val; | |
break; | |
case 12: /* Clear Byte Pointer Flip/Flop */ | |
dma_state.bff = 0; | |
break; | |
case 13: /* Master Clear */ | |
dma_state.bff = 0; | |
dma_state.command = 0; | |
dma_state.status = 0; | |
for (i = 0; i < 4; i++) { | |
dma_state.channels[i].addr = 0; | |
dma_state.channels[i].wcount = 0; | |
dma_state.channels[i].page = 0; | |
} | |
break; | |
case 15: /* Write All Mask Register Bits */ | |
sim_debug(WRITE_MSG, &dmac_dev, | |
"[%08x] Clear DMAC Interrupt. val=%02x\n", | |
R[NUM_PC], val); | |
dma_state.mask = val & 0xf; | |
break; | |
case 16: /* Clear DMAC Interrupt */ | |
sim_debug(WRITE_MSG, &dmac_dev, | |
"[%08x] Clear DMAC Interrupt in DMAC. val=%02x\n", | |
R[NUM_PC], val); | |
break; | |
default: | |
sim_debug(WRITE_MSG, &dmac_dev, | |
"[%08x] Unhandled DMAC write. reg=%x val=%02x\n", | |
R[NUM_PC], reg, val); | |
break; | |
} | |
} | |
void dmac_page_update(uint8 base, uint8 reg, uint8 val) | |
{ | |
uint8 shift = 0; | |
/* Sanity check */ | |
if (reg > 3) { | |
return; | |
} | |
/* The actual register is a 32-bit, byte-addressed register, so | |
that address 4x000 is the highest byte, 4x003 is the lowest | |
byte. */ | |
shift = -(reg - 3) * 8; | |
switch (base) { | |
case DMA_ID: | |
sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 0 = %x\n", val); | |
dma_state.channels[DMA_ID_CHAN].page &= ~(0xff << shift); | |
dma_state.channels[DMA_ID_CHAN].page |= (val << shift); | |
break; | |
case DMA_IF: | |
sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 1 = %x\n", val); | |
dma_state.channels[DMA_IF_CHAN].page &= ~(0xff << shift); | |
dma_state.channels[DMA_IF_CHAN].page |= (val << shift); | |
break; | |
case DMA_IUA: | |
sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 2 = %x\n", val); | |
dma_state.channels[DMA_IUA_CHAN].page &= ~(0xff << shift); | |
dma_state.channels[DMA_IUA_CHAN].page |= (val << shift); | |
break; | |
case DMA_IUB: | |
sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 3 = %x\n", val); | |
dma_state.channels[DMA_IUB_CHAN].page &= ~(0xff << shift); | |
dma_state.channels[DMA_IUB_CHAN].page |= (val << shift); | |
break; | |
} | |
} | |
void dmac_write(uint32 pa, uint32 val, size_t size) | |
{ | |
uint8 reg, base; | |
base = (uint8) (pa >> 12); | |
reg = pa & 0xff; | |
switch (base) { | |
case DMA_C: /* 0x48xxx */ | |
dmac_program(reg, (uint8) val); | |
break; | |
case DMA_ID: /* 0x45xxx */ | |
case DMA_IUA: /* 0x46xxx */ | |
case DMA_IUB: /* 0x47xxx */ | |
case DMA_IF: /* 0x4Exxx */ | |
dmac_page_update(base, reg, (uint8) val); | |
break; | |
} | |
} | |
static SIM_INLINE uint32 dma_address(uint8 channel, uint32 offset, t_bool r) { | |
uint32 addr; | |
addr = (PHYS_MEM_BASE + | |
dma_state.channels[channel].addr + | |
offset); | |
/* The top bit of the page address is a R/W bit, so we mask it here */ | |
addr |= (uint32) (((uint32)dma_state.channels[channel].page & 0x7f) << 16); | |
return addr; | |
} | |
void dmac_transfer(uint8 channel, uint32 service_address) | |
{ | |
uint8 data; | |
int32 i; | |
uint16 offset; | |
uint32 addr; | |
dma_channel *chan = &dma_state.channels[channel]; | |
/* TODO: This does not handle decrement-mode transfers, | |
which don't seem to be used in SVR3 */ | |
switch ((dma_state.mode >> 2) & 0xf) { | |
case DMA_MODE_VERIFY: | |
sim_debug(EXECUTE_MSG, &dmac_dev, | |
"[%08x] [dmac_transfer channel=%d] unhandled VERIFY request.\n", | |
R[NUM_PC], channel); | |
break; | |
case DMA_MODE_WRITE: | |
sim_debug(EXECUTE_MSG, &dmac_dev, | |
"[%08x] [dmac_transfer channel=%d] write: %d bytes from %08x\n", | |
R[NUM_PC], channel, | |
chan->wcount + 1, | |
dma_address(channel, 0, TRUE)); | |
offset = 0; | |
for (i = chan->wcount; i >= 0; i--) { | |
addr = dma_address(channel, offset, TRUE); | |
chan->addr_c = dma_state.channels[channel].addr + offset; | |
offset++; | |
data = pread_b(service_address); | |
write_b(addr, data); | |
} | |
break; | |
case DMA_MODE_READ: | |
sim_debug(EXECUTE_MSG, &dmac_dev, | |
"[%08x] [dmac_transfer channel=%d] read: %d bytes to %08x\n", | |
R[NUM_PC], channel, | |
chan->wcount + 1, | |
dma_address(channel, 0, TRUE)); | |
offset = 0; | |
for (i = chan->wcount; i >= 0; i--) { | |
addr = dma_address(channel, offset++, TRUE); | |
chan->addr_c = dma_state.channels[channel].addr + offset; | |
data = pread_b(addr); | |
write_b(service_address, data); | |
} | |
break; | |
} | |
/* End of Process must set the IF channel's mask bit */ | |
dma_state.mask |= (1 << channel); | |
dma_state.status |= (1 << channel); | |
} | |
/* | |
* Service pending DRQs | |
*/ | |
void dmac_service_drqs() | |
{ | |
dmac_drq_handler *h; | |
for (h = &dmac_drq_handlers[0]; h->drq != NULL; h++) { | |
/* Only trigger if the channel has a DRQ set and its channel's | |
mask bit is 0 */ | |
if (*h->drq && ((dma_state.mask >> h->channel) & 0x1) == 0) { | |
dmac_transfer(h->channel, h->service_address); | |
*h->drq = FALSE; /* Immediately clear DRQ state */ | |
if (h->handled_callback != NULL) { | |
h->handled_callback(); | |
} | |
} | |
} | |
} |