/* * Copyright (c) 2013 ARM Limited * All rights reserved * * The license below extends only to copyright in the software and shall * not be construed as granting a license to any other intellectual * property including but not limited to intellectual property relating * to a hardware implementation of the functionality of the software * licensed hereunder. You may use the software subject to the license * terms below provided that you ensure that this notice is replicated * unmodified and in its entirety in all distributions of the software, * modified or unmodified, in source code or in binary form. * * Copyright (c) 2004-2005 The Regents of The University of Michigan * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer; * redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution; * neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Authors: Andrew Schultz * Ali Saidi */ /** @file * Device model implementation for an IDE disk */ #include "dev/storage/ide_disk.hh" #include #include #include #include #include "arch/isa_traits.hh" #include "base/chunk_generator.hh" #include "base/cprintf.hh" // csprintf #include "base/trace.hh" #include "config/the_isa.hh" #include "debug/IdeDisk.hh" #include "dev/storage/disk_image.hh" #include "dev/storage/ide_ctrl.hh" #include "sim/core.hh" #include "sim/sim_object.hh" using namespace std; using namespace TheISA; IdeDisk::IdeDisk(const Params *p) : SimObject(p), ctrl(NULL), image(p->image), diskDelay(p->delay), dmaTransferEvent([this]{ doDmaTransfer(); }, name()), dmaReadCG(NULL), dmaReadWaitEvent([this]{ doDmaRead(); }, name()), dmaWriteCG(NULL), dmaWriteWaitEvent([this]{ doDmaWrite(); }, name()), dmaPrdReadEvent([this]{ dmaPrdReadDone(); }, name()), dmaReadEvent([this]{ dmaReadDone(); }, name()), dmaWriteEvent([this]{ dmaWriteDone(); }, name()) { // Reset the device state reset(p->driveID); // fill out the drive ID structure memset(&driveID, 0, sizeof(struct ataparams)); // Calculate LBA and C/H/S values uint16_t cylinders; uint8_t heads; uint8_t sectors; uint32_t lba_size = image->size(); if (lba_size >= 16383*16*63) { cylinders = 16383; heads = 16; sectors = 63; } else { if (lba_size >= 63) sectors = 63; else if (lba_size == 0) panic("Bad IDE image size: 0\n"); else sectors = lba_size; if ((lba_size / sectors) >= 16) heads = 16; else heads = (lba_size / sectors); cylinders = lba_size / (heads * sectors); } // Setup the model name strncpy((char *)driveID.atap_model, "5MI EDD si k", sizeof(driveID.atap_model)); // Set the maximum multisector transfer size driveID.atap_multi = MAX_MULTSECT; // IORDY supported, IORDY disabled, LBA enabled, DMA enabled driveID.atap_capabilities1 = 0x7; // UDMA support, EIDE support driveID.atap_extensions = 0x6; // Setup default C/H/S settings driveID.atap_cylinders = cylinders; driveID.atap_sectors = sectors; driveID.atap_heads = heads; // Setup the current multisector transfer size driveID.atap_curmulti = MAX_MULTSECT; driveID.atap_curmulti_valid = 0x1; // Number of sectors on disk driveID.atap_capacity = lba_size; // Multiword DMA mode 2 and below supported driveID.atap_dmamode_supp = 0x4; // Set PIO mode 4 and 3 supported driveID.atap_piomode_supp = 0x3; // Set DMA mode 4 and below supported driveID.atap_udmamode_supp = 0x1f; // Statically set hardware config word driveID.atap_hwreset_res = 0x4001; //arbitrary for now... driveID.atap_ata_major = WDC_VER_ATA7; } IdeDisk::~IdeDisk() { // destroy the data buffer delete [] dataBuffer; } void IdeDisk::reset(int id) { // initialize the data buffer and shadow registers dataBuffer = new uint8_t[MAX_DMA_SIZE]; memset(dataBuffer, 0, MAX_DMA_SIZE); memset(&cmdReg, 0, sizeof(CommandReg_t)); memset(&curPrd.entry, 0, sizeof(PrdEntry_t)); curPrdAddr = 0; curSector = 0; cmdBytes = 0; cmdBytesLeft = 0; drqBytesLeft = 0; dmaRead = false; intrPending = false; dmaAborted = false; // set the device state to idle dmaState = Dma_Idle; if (id == DEV0) { devState = Device_Idle_S; devID = DEV0; } else if (id == DEV1) { devState = Device_Idle_NS; devID = DEV1; } else { panic("Invalid device ID: %#x\n", id); } // set the device ready bit status = STATUS_DRDY_BIT; /* The error register must be set to 0x1 on start-up to indicate that no diagnostic error was detected */ cmdReg.error = 0x1; } //// // Utility functions //// bool IdeDisk::isDEVSelect() { return ctrl->isDiskSelected(this); } Addr IdeDisk::pciToDma(Addr pciAddr) { if (ctrl) return ctrl->pciToDma(pciAddr); else panic("Access to unset controller!\n"); } //// // Device registers read/write //// void IdeDisk::readCommand(const Addr offset, int size, uint8_t *data) { if (offset == DATA_OFFSET) { if (size == sizeof(uint16_t)) { *(uint16_t *)data = cmdReg.data; } else if (size == sizeof(uint32_t)) { *(uint16_t *)data = cmdReg.data; updateState(ACT_DATA_READ_SHORT); *((uint16_t *)data + 1) = cmdReg.data; } else { panic("Data read of unsupported size %d.\n", size); } updateState(ACT_DATA_READ_SHORT); return; } assert(size == sizeof(uint8_t)); switch (offset) { case ERROR_OFFSET: *data = cmdReg.error; break; case NSECTOR_OFFSET: *data = cmdReg.sec_count; break; case SECTOR_OFFSET: *data = cmdReg.sec_num; break; case LCYL_OFFSET: *data = cmdReg.cyl_low; break; case HCYL_OFFSET: *data = cmdReg.cyl_high; break; case DRIVE_OFFSET: *data = cmdReg.drive; break; case STATUS_OFFSET: *data = status; updateState(ACT_STAT_READ); break; default: panic("Invalid IDE command register offset: %#x\n", offset); } DPRINTF(IdeDisk, "Read to disk at offset: %#x data %#x\n", offset, *data); } void IdeDisk::readControl(const Addr offset, int size, uint8_t *data) { assert(size == sizeof(uint8_t)); *data = status; if (offset != ALTSTAT_OFFSET) panic("Invalid IDE control register offset: %#x\n", offset); DPRINTF(IdeDisk, "Read to disk at offset: %#x data %#x\n", offset, *data); } void IdeDisk::writeCommand(const Addr offset, int size, const uint8_t *data) { if (offset == DATA_OFFSET) { if (size == sizeof(uint16_t)) { cmdReg.data = *(const uint16_t *)data; } else if (size == sizeof(uint32_t)) { cmdReg.data = *(const uint16_t *)data; updateState(ACT_DATA_WRITE_SHORT); cmdReg.data = *((const uint16_t *)data + 1); } else { panic("Data write of unsupported size %d.\n", size); } updateState(ACT_DATA_WRITE_SHORT); return; } assert(size == sizeof(uint8_t)); switch (offset) { case FEATURES_OFFSET: break; case NSECTOR_OFFSET: cmdReg.sec_count = *data; break; case SECTOR_OFFSET: cmdReg.sec_num = *data; break; case LCYL_OFFSET: cmdReg.cyl_low = *data; break; case HCYL_OFFSET: cmdReg.cyl_high = *data; break; case DRIVE_OFFSET: cmdReg.drive = *data; updateState(ACT_SELECT_WRITE); break; case COMMAND_OFFSET: cmdReg.command = *data; updateState(ACT_CMD_WRITE); break; default: panic("Invalid IDE command register offset: %#x\n", offset); } DPRINTF(IdeDisk, "Write to disk at offset: %#x data %#x\n", offset, (uint32_t)*data); } void IdeDisk::writeControl(const Addr offset, int size, const uint8_t *data) { if (offset != CONTROL_OFFSET) panic("Invalid IDE control register offset: %#x\n", offset); if (*data & CONTROL_RST_BIT) { // force the device into the reset state devState = Device_Srst; updateState(ACT_SRST_SET); } else if (devState == Device_Srst && !(*data & CONTROL_RST_BIT)) { updateState(ACT_SRST_CLEAR); } nIENBit = *data & CONTROL_IEN_BIT; DPRINTF(IdeDisk, "Write to disk at offset: %#x data %#x\n", offset, (uint32_t)*data); } //// // Perform DMA transactions //// void IdeDisk::doDmaTransfer() { if (dmaAborted) { DPRINTF(IdeDisk, "DMA Aborted before reading PRD entry\n"); updateState(ACT_CMD_ERROR); return; } if (dmaState != Dma_Transfer || devState != Transfer_Data_Dma) panic("Inconsistent DMA transfer state: dmaState = %d devState = %d\n", dmaState, devState); if (ctrl->dmaPending() || ctrl->drainState() != DrainState::Running) { schedule(dmaTransferEvent, curTick() + DMA_BACKOFF_PERIOD); return; } else ctrl->dmaRead(curPrdAddr, sizeof(PrdEntry_t), &dmaPrdReadEvent, (uint8_t*)&curPrd.entry); } void IdeDisk::dmaPrdReadDone() { if (dmaAborted) { DPRINTF(IdeDisk, "DMA Aborted while reading PRD entry\n"); updateState(ACT_CMD_ERROR); return; } DPRINTF(IdeDisk, "PRD: baseAddr:%#x (%#x) byteCount:%d (%d) eot:%#x sector:%d\n", curPrd.getBaseAddr(), pciToDma(curPrd.getBaseAddr()), curPrd.getByteCount(), (cmdBytesLeft/SectorSize), curPrd.getEOT(), curSector); // the prd pointer has already been translated, so just do an increment curPrdAddr = curPrdAddr + sizeof(PrdEntry_t); if (dmaRead) doDmaDataRead(); else doDmaDataWrite(); } void IdeDisk::doDmaDataRead() { /** @todo we need to figure out what the delay actually will be */ Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize); DPRINTF(IdeDisk, "doDmaRead, diskDelay: %d totalDiskDelay: %d\n", diskDelay, totalDiskDelay); schedule(dmaReadWaitEvent, curTick() + totalDiskDelay); } void IdeDisk::regStats() { SimObject::regStats(); using namespace Stats; dmaReadFullPages .name(name() + ".dma_read_full_pages") .desc("Number of full page size DMA reads (not PRD).") ; dmaReadBytes .name(name() + ".dma_read_bytes") .desc("Number of bytes transfered via DMA reads (not PRD).") ; dmaReadTxs .name(name() + ".dma_read_txs") .desc("Number of DMA read transactions (not PRD).") ; dmaWriteFullPages .name(name() + ".dma_write_full_pages") .desc("Number of full page size DMA writes.") ; dmaWriteBytes .name(name() + ".dma_write_bytes") .desc("Number of bytes transfered via DMA writes.") ; dmaWriteTxs .name(name() + ".dma_write_txs") .desc("Number of DMA write transactions.") ; } void IdeDisk::doDmaRead() { if (dmaAborted) { DPRINTF(IdeDisk, "DMA Aborted in middle of Dma Read\n"); if (dmaReadCG) delete dmaReadCG; dmaReadCG = NULL; updateState(ACT_CMD_ERROR); return; } if (!dmaReadCG) { // clear out the data buffer memset(dataBuffer, 0, MAX_DMA_SIZE); dmaReadCG = new ChunkGenerator(curPrd.getBaseAddr(), curPrd.getByteCount(), TheISA::PageBytes); } if (ctrl->dmaPending() || ctrl->drainState() != DrainState::Running) { schedule(dmaReadWaitEvent, curTick() + DMA_BACKOFF_PERIOD); return; } else if (!dmaReadCG->done()) { assert(dmaReadCG->complete() < MAX_DMA_SIZE); ctrl->dmaRead(pciToDma(dmaReadCG->addr()), dmaReadCG->size(), &dmaReadWaitEvent, dataBuffer + dmaReadCG->complete()); dmaReadBytes += dmaReadCG->size(); dmaReadTxs++; if (dmaReadCG->size() == TheISA::PageBytes) dmaReadFullPages++; dmaReadCG->next(); } else { assert(dmaReadCG->done()); delete dmaReadCG; dmaReadCG = NULL; dmaReadDone(); } } void IdeDisk::dmaReadDone() { uint32_t bytesWritten = 0; // write the data to the disk image for (bytesWritten = 0; bytesWritten < curPrd.getByteCount(); bytesWritten += SectorSize) { cmdBytesLeft -= SectorSize; writeDisk(curSector++, (uint8_t *)(dataBuffer + bytesWritten)); } // check for the EOT if (curPrd.getEOT()) { assert(cmdBytesLeft == 0); dmaState = Dma_Idle; updateState(ACT_DMA_DONE); } else { doDmaTransfer(); } } void IdeDisk::doDmaDataWrite() { /** @todo we need to figure out what the delay actually will be */ Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize); uint32_t bytesRead = 0; DPRINTF(IdeDisk, "doDmaWrite, diskDelay: %d totalDiskDelay: %d\n", diskDelay, totalDiskDelay); memset(dataBuffer, 0, MAX_DMA_SIZE); assert(cmdBytesLeft <= MAX_DMA_SIZE); while (bytesRead < curPrd.getByteCount()) { readDisk(curSector++, (uint8_t *)(dataBuffer + bytesRead)); bytesRead += SectorSize; cmdBytesLeft -= SectorSize; } DPRINTF(IdeDisk, "doDmaWrite, bytesRead: %d cmdBytesLeft: %d\n", bytesRead, cmdBytesLeft); schedule(dmaWriteWaitEvent, curTick() + totalDiskDelay); } void IdeDisk::doDmaWrite() { if (dmaAborted) { DPRINTF(IdeDisk, "DMA Aborted while doing DMA Write\n"); if (dmaWriteCG) delete dmaWriteCG; dmaWriteCG = NULL; updateState(ACT_CMD_ERROR); return; } if (!dmaWriteCG) { // clear out the data buffer dmaWriteCG = new ChunkGenerator(curPrd.getBaseAddr(), curPrd.getByteCount(), TheISA::PageBytes); } if (ctrl->dmaPending() || ctrl->drainState() != DrainState::Running) { schedule(dmaWriteWaitEvent, curTick() + DMA_BACKOFF_PERIOD); DPRINTF(IdeDisk, "doDmaWrite: rescheduling\n"); return; } else if (!dmaWriteCG->done()) { assert(dmaWriteCG->complete() < MAX_DMA_SIZE); ctrl->dmaWrite(pciToDma(dmaWriteCG->addr()), dmaWriteCG->size(), &dmaWriteWaitEvent, dataBuffer + dmaWriteCG->complete()); DPRINTF(IdeDisk, "doDmaWrite: not done curPrd byte count %d, eot %#x\n", curPrd.getByteCount(), curPrd.getEOT()); dmaWriteBytes += dmaWriteCG->size(); dmaWriteTxs++; if (dmaWriteCG->size() == TheISA::PageBytes) dmaWriteFullPages++; dmaWriteCG->next(); } else { DPRINTF(IdeDisk, "doDmaWrite: done curPrd byte count %d, eot %#x\n", curPrd.getByteCount(), curPrd.getEOT()); assert(dmaWriteCG->done()); delete dmaWriteCG; dmaWriteCG = NULL; dmaWriteDone(); } } void IdeDisk::dmaWriteDone() { DPRINTF(IdeDisk, "doWriteDone: curPrd byte count %d, eot %#x cmd bytes left:%d\n", curPrd.getByteCount(), curPrd.getEOT(), cmdBytesLeft); // check for the EOT if (curPrd.getEOT()) { assert(cmdBytesLeft == 0); dmaState = Dma_Idle; updateState(ACT_DMA_DONE); } else { doDmaTransfer(); } } //// // Disk utility routines /// void IdeDisk::readDisk(uint32_t sector, uint8_t *data) { uint32_t bytesRead = image->read(data, sector); if (bytesRead != SectorSize) panic("Can't read from %s. Only %d of %d read. errno=%d\n", name(), bytesRead, SectorSize, errno); } void IdeDisk::writeDisk(uint32_t sector, uint8_t *data) { uint32_t bytesWritten = image->write(data, sector); if (bytesWritten != SectorSize) panic("Can't write to %s. Only %d of %d written. errno=%d\n", name(), bytesWritten, SectorSize, errno); } //// // Setup and handle commands //// void IdeDisk::startDma(const uint32_t &prdTableBase) { if (dmaState != Dma_Start) panic("Inconsistent DMA state, should be in Dma_Start!\n"); if (devState != Transfer_Data_Dma) panic("Inconsistent device state for DMA start!\n"); // PRD base address is given by bits 31:2 curPrdAddr = pciToDma((Addr)(prdTableBase & ~ULL(0x3))); dmaState = Dma_Transfer; // schedule dma transfer (doDmaTransfer) schedule(dmaTransferEvent, curTick() + 1); } void IdeDisk::abortDma() { if (dmaState == Dma_Idle) panic("Inconsistent DMA state, should be Start or Transfer!"); if (devState != Transfer_Data_Dma && devState != Prepare_Data_Dma) panic("Inconsistent device state, should be Transfer or Prepare!\n"); updateState(ACT_CMD_ERROR); } void IdeDisk::startCommand() { DevAction_t action = ACT_NONE; uint32_t size = 0; dmaRead = false; // Decode commands switch (cmdReg.command) { // Supported non-data commands case WDSF_READ_NATIVE_MAX: size = (uint32_t)image->size() - 1; cmdReg.sec_num = (size & 0xff); cmdReg.cyl_low = ((size & 0xff00) >> 8); cmdReg.cyl_high = ((size & 0xff0000) >> 16); cmdReg.head = ((size & 0xf000000) >> 24); devState = Command_Execution; action = ACT_CMD_COMPLETE; break; case WDCC_RECAL: case WDCC_IDP: case WDCC_STANDBY_IMMED: case WDCC_FLUSHCACHE: case WDSF_VERIFY: case WDSF_SEEK: case SET_FEATURES: case WDCC_SETMULTI: case WDCC_IDLE: devState = Command_Execution; action = ACT_CMD_COMPLETE; break; // Supported PIO data-in commands case WDCC_IDENTIFY: case ATAPI_IDENTIFY_DEVICE: cmdBytes = cmdBytesLeft = sizeof(struct ataparams); devState = Prepare_Data_In; action = ACT_DATA_READY; break; case WDCC_READMULTI: case WDCC_READ: if (!(cmdReg.drive & DRIVE_LBA_BIT)) panic("Attempt to perform CHS access, only supports LBA\n"); if (cmdReg.sec_count == 0) cmdBytes = cmdBytesLeft = (256 * SectorSize); else cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize); curSector = getLBABase(); /** @todo make this a scheduled event to simulate disk delay */ devState = Prepare_Data_In; action = ACT_DATA_READY; break; // Supported PIO data-out commands case WDCC_WRITEMULTI: case WDCC_WRITE: if (!(cmdReg.drive & DRIVE_LBA_BIT)) panic("Attempt to perform CHS access, only supports LBA\n"); if (cmdReg.sec_count == 0) cmdBytes = cmdBytesLeft = (256 * SectorSize); else cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize); DPRINTF(IdeDisk, "Setting cmdBytesLeft to %d\n", cmdBytesLeft); curSector = getLBABase(); devState = Prepare_Data_Out; action = ACT_DATA_READY; break; // Supported DMA commands case WDCC_WRITEDMA: dmaRead = true; // a write to the disk is a DMA read from memory M5_FALLTHROUGH; case WDCC_READDMA: if (!(cmdReg.drive & DRIVE_LBA_BIT)) panic("Attempt to perform CHS access, only supports LBA\n"); if (cmdReg.sec_count == 0) cmdBytes = cmdBytesLeft = (256 * SectorSize); else cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize); DPRINTF(IdeDisk, "Setting cmdBytesLeft to %d in readdma\n", cmdBytesLeft); curSector = getLBABase(); devState = Prepare_Data_Dma; action = ACT_DMA_READY; break; default: panic("Unsupported ATA command: %#x\n", cmdReg.command); } if (action != ACT_NONE) { // set the BSY bit status |= STATUS_BSY_BIT; // clear the DRQ bit status &= ~STATUS_DRQ_BIT; // clear the DF bit status &= ~STATUS_DF_BIT; updateState(action); } } //// // Handle setting and clearing interrupts //// void IdeDisk::intrPost() { DPRINTF(IdeDisk, "Posting Interrupt\n"); if (intrPending) panic("Attempt to post an interrupt with one pending\n"); intrPending = true; // talk to controller to set interrupt if (ctrl) { ctrl->intrPost(); } } void IdeDisk::intrClear() { DPRINTF(IdeDisk, "Clearing Interrupt\n"); if (!intrPending) panic("Attempt to clear a non-pending interrupt\n"); intrPending = false; // talk to controller to clear interrupt if (ctrl) ctrl->intrClear(); } //// // Manage the device internal state machine //// void IdeDisk::updateState(DevAction_t action) { switch (devState) { case Device_Srst: if (action == ACT_SRST_SET) { // set the BSY bit status |= STATUS_BSY_BIT; } else if (action == ACT_SRST_CLEAR) { // clear the BSY bit status &= ~STATUS_BSY_BIT; // reset the device state reset(devID); } break; case Device_Idle_S: if (action == ACT_SELECT_WRITE && !isDEVSelect()) { devState = Device_Idle_NS; } else if (action == ACT_CMD_WRITE) { startCommand(); } break; case Device_Idle_SI: if (action == ACT_SELECT_WRITE && !isDEVSelect()) { devState = Device_Idle_NS; intrClear(); } else if (action == ACT_STAT_READ || isIENSet()) { devState = Device_Idle_S; intrClear(); } else if (action == ACT_CMD_WRITE) { intrClear(); startCommand(); } break; case Device_Idle_NS: if (action == ACT_SELECT_WRITE && isDEVSelect()) { if (!isIENSet() && intrPending) { devState = Device_Idle_SI; intrPost(); } if (isIENSet() || !intrPending) { devState = Device_Idle_S; } } break; case Command_Execution: if (action == ACT_CMD_COMPLETE) { // clear the BSY bit setComplete(); if (!isIENSet()) { devState = Device_Idle_SI; intrPost(); } else { devState = Device_Idle_S; } } break; case Prepare_Data_In: if (action == ACT_CMD_ERROR) { // clear the BSY bit setComplete(); if (!isIENSet()) { devState = Device_Idle_SI; intrPost(); } else { devState = Device_Idle_S; } } else if (action == ACT_DATA_READY) { // clear the BSY bit status &= ~STATUS_BSY_BIT; // set the DRQ bit status |= STATUS_DRQ_BIT; // copy the data into the data buffer if (cmdReg.command == WDCC_IDENTIFY || cmdReg.command == ATAPI_IDENTIFY_DEVICE) { // Reset the drqBytes for this block drqBytesLeft = sizeof(struct ataparams); memcpy((void *)dataBuffer, (void *)&driveID, sizeof(struct ataparams)); } else { // Reset the drqBytes for this block drqBytesLeft = SectorSize; readDisk(curSector++, dataBuffer); } // put the first two bytes into the data register memcpy((void *)&cmdReg.data, (void *)dataBuffer, sizeof(uint16_t)); if (!isIENSet()) { devState = Data_Ready_INTRQ_In; intrPost(); } else { devState = Transfer_Data_In; } } break; case Data_Ready_INTRQ_In: if (action == ACT_STAT_READ) { devState = Transfer_Data_In; intrClear(); } break; case Transfer_Data_In: if (action == ACT_DATA_READ_BYTE || action == ACT_DATA_READ_SHORT) { if (action == ACT_DATA_READ_BYTE) { panic("DEBUG: READING DATA ONE BYTE AT A TIME!\n"); } else { drqBytesLeft -= 2; cmdBytesLeft -= 2; // copy next short into data registers if (drqBytesLeft) memcpy((void *)&cmdReg.data, (void *)&dataBuffer[SectorSize - drqBytesLeft], sizeof(uint16_t)); } if (drqBytesLeft == 0) { if (cmdBytesLeft == 0) { // Clear the BSY bit setComplete(); devState = Device_Idle_S; } else { devState = Prepare_Data_In; // set the BSY_BIT status |= STATUS_BSY_BIT; // clear the DRQ_BIT status &= ~STATUS_DRQ_BIT; /** @todo change this to a scheduled event to simulate disk delay */ updateState(ACT_DATA_READY); } } } break; case Prepare_Data_Out: if (action == ACT_CMD_ERROR || cmdBytesLeft == 0) { // clear the BSY bit setComplete(); if (!isIENSet()) { devState = Device_Idle_SI; intrPost(); } else { devState = Device_Idle_S; } } else if (action == ACT_DATA_READY && cmdBytesLeft != 0) { // clear the BSY bit status &= ~STATUS_BSY_BIT; // set the DRQ bit status |= STATUS_DRQ_BIT; // clear the data buffer to get it ready for writes memset(dataBuffer, 0, MAX_DMA_SIZE); // reset the drqBytes for this block drqBytesLeft = SectorSize; if (cmdBytesLeft == cmdBytes || isIENSet()) { devState = Transfer_Data_Out; } else { devState = Data_Ready_INTRQ_Out; intrPost(); } } break; case Data_Ready_INTRQ_Out: if (action == ACT_STAT_READ) { devState = Transfer_Data_Out; intrClear(); } break; case Transfer_Data_Out: if (action == ACT_DATA_WRITE_BYTE || action == ACT_DATA_WRITE_SHORT) { if (action == ACT_DATA_READ_BYTE) { panic("DEBUG: WRITING DATA ONE BYTE AT A TIME!\n"); } else { // copy the latest short into the data buffer memcpy((void *)&dataBuffer[SectorSize - drqBytesLeft], (void *)&cmdReg.data, sizeof(uint16_t)); drqBytesLeft -= 2; cmdBytesLeft -= 2; } if (drqBytesLeft == 0) { // copy the block to the disk writeDisk(curSector++, dataBuffer); // set the BSY bit status |= STATUS_BSY_BIT; // set the seek bit status |= STATUS_SEEK_BIT; // clear the DRQ bit status &= ~STATUS_DRQ_BIT; devState = Prepare_Data_Out; /** @todo change this to a scheduled event to simulate disk delay */ updateState(ACT_DATA_READY); } } break; case Prepare_Data_Dma: if (action == ACT_CMD_ERROR) { // clear the BSY bit setComplete(); if (!isIENSet()) { devState = Device_Idle_SI; intrPost(); } else { devState = Device_Idle_S; } } else if (action == ACT_DMA_READY) { // clear the BSY bit status &= ~STATUS_BSY_BIT; // set the DRQ bit status |= STATUS_DRQ_BIT; devState = Transfer_Data_Dma; if (dmaState != Dma_Idle) panic("Inconsistent DMA state, should be Dma_Idle\n"); dmaState = Dma_Start; // wait for the write to the DMA start bit } break; case Transfer_Data_Dma: if (action == ACT_CMD_ERROR) { dmaAborted = true; devState = Device_Dma_Abort; } else if (action == ACT_DMA_DONE) { // clear the BSY bit setComplete(); // set the seek bit status |= STATUS_SEEK_BIT; // clear the controller state for DMA transfer ctrl->setDmaComplete(this); if (!isIENSet()) { devState = Device_Idle_SI; intrPost(); } else { devState = Device_Idle_S; } } break; case Device_Dma_Abort: if (action == ACT_CMD_ERROR) { setComplete(); status |= STATUS_SEEK_BIT; ctrl->setDmaComplete(this); dmaAborted = false; dmaState = Dma_Idle; if (!isIENSet()) { devState = Device_Idle_SI; intrPost(); } else { devState = Device_Idle_S; } } else { DPRINTF(IdeDisk, "Disk still busy aborting previous DMA command\n"); } break; default: panic("Unknown IDE device state: %#x\n", devState); } } void IdeDisk::serialize(CheckpointOut &cp) const { // Check all outstanding events to see if they are scheduled // these are all mutually exclusive Tick reschedule = 0; Events_t event = None; int eventCount = 0; if (dmaTransferEvent.scheduled()) { reschedule = dmaTransferEvent.when(); event = Transfer; eventCount++; } if (dmaReadWaitEvent.scheduled()) { reschedule = dmaReadWaitEvent.when(); event = ReadWait; eventCount++; } if (dmaWriteWaitEvent.scheduled()) { reschedule = dmaWriteWaitEvent.when(); event = WriteWait; eventCount++; } if (dmaPrdReadEvent.scheduled()) { reschedule = dmaPrdReadEvent.when(); event = PrdRead; eventCount++; } if (dmaReadEvent.scheduled()) { reschedule = dmaReadEvent.when(); event = DmaRead; eventCount++; } if (dmaWriteEvent.scheduled()) { reschedule = dmaWriteEvent.when(); event = DmaWrite; eventCount++; } assert(eventCount <= 1); SERIALIZE_SCALAR(reschedule); SERIALIZE_ENUM(event); // Serialize device registers SERIALIZE_SCALAR(cmdReg.data); SERIALIZE_SCALAR(cmdReg.sec_count); SERIALIZE_SCALAR(cmdReg.sec_num); SERIALIZE_SCALAR(cmdReg.cyl_low); SERIALIZE_SCALAR(cmdReg.cyl_high); SERIALIZE_SCALAR(cmdReg.drive); SERIALIZE_SCALAR(cmdReg.command); SERIALIZE_SCALAR(status); SERIALIZE_SCALAR(nIENBit); SERIALIZE_SCALAR(devID); // Serialize the PRD related information SERIALIZE_SCALAR(curPrd.entry.baseAddr); SERIALIZE_SCALAR(curPrd.entry.byteCount); SERIALIZE_SCALAR(curPrd.entry.endOfTable); SERIALIZE_SCALAR(curPrdAddr); /** @todo need to serialized chunk generator stuff!! */ // Serialize current transfer related information SERIALIZE_SCALAR(cmdBytesLeft); SERIALIZE_SCALAR(cmdBytes); SERIALIZE_SCALAR(drqBytesLeft); SERIALIZE_SCALAR(curSector); SERIALIZE_SCALAR(dmaRead); SERIALIZE_SCALAR(intrPending); SERIALIZE_SCALAR(dmaAborted); SERIALIZE_ENUM(devState); SERIALIZE_ENUM(dmaState); SERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE); } void IdeDisk::unserialize(CheckpointIn &cp) { // Reschedule events that were outstanding // these are all mutually exclusive Tick reschedule = 0; Events_t event = None; UNSERIALIZE_SCALAR(reschedule); UNSERIALIZE_ENUM(event); switch (event) { case None : break; case Transfer : schedule(dmaTransferEvent, reschedule); break; case ReadWait : schedule(dmaReadWaitEvent, reschedule); break; case WriteWait : schedule(dmaWriteWaitEvent, reschedule); break; case PrdRead : schedule(dmaPrdReadEvent, reschedule); break; case DmaRead : schedule(dmaReadEvent, reschedule); break; case DmaWrite : schedule(dmaWriteEvent, reschedule); break; } // Unserialize device registers UNSERIALIZE_SCALAR(cmdReg.data); UNSERIALIZE_SCALAR(cmdReg.sec_count); UNSERIALIZE_SCALAR(cmdReg.sec_num); UNSERIALIZE_SCALAR(cmdReg.cyl_low); UNSERIALIZE_SCALAR(cmdReg.cyl_high); UNSERIALIZE_SCALAR(cmdReg.drive); UNSERIALIZE_SCALAR(cmdReg.command); UNSERIALIZE_SCALAR(status); UNSERIALIZE_SCALAR(nIENBit); UNSERIALIZE_SCALAR(devID); // Unserialize the PRD related information UNSERIALIZE_SCALAR(curPrd.entry.baseAddr); UNSERIALIZE_SCALAR(curPrd.entry.byteCount); UNSERIALIZE_SCALAR(curPrd.entry.endOfTable); UNSERIALIZE_SCALAR(curPrdAddr); /** @todo need to serialized chunk generator stuff!! */ // Unserialize current transfer related information UNSERIALIZE_SCALAR(cmdBytes); UNSERIALIZE_SCALAR(cmdBytesLeft); UNSERIALIZE_SCALAR(drqBytesLeft); UNSERIALIZE_SCALAR(curSector); UNSERIALIZE_SCALAR(dmaRead); UNSERIALIZE_SCALAR(intrPending); UNSERIALIZE_SCALAR(dmaAborted); UNSERIALIZE_ENUM(devState); UNSERIALIZE_ENUM(dmaState); UNSERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE); } IdeDisk * IdeDiskParams::create() { return new IdeDisk(this); }