/* 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_console.drq, iua_drq_handled}, {DMA_IUB_CHAN, IUBASE+IUB_DATA_REG, &iu_contty.drq, 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]; 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); /* Set the appropriate DRQ */ /* *dmac_drq_handlers[channel_id].drq = TRUE; */ } 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] Write DMAC mask (all bits). 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(); } } } }