diff options
Diffstat (limited to 'src/dev/ide_disk.cc')
-rw-r--r-- | src/dev/ide_disk.cc | 1149 |
1 files changed, 1149 insertions, 0 deletions
diff --git a/src/dev/ide_disk.cc b/src/dev/ide_disk.cc new file mode 100644 index 000000000..dc78021f8 --- /dev/null +++ b/src/dev/ide_disk.cc @@ -0,0 +1,1149 @@ +/* + * 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 <cerrno> +#include <cstring> +#include <deque> +#include <string> + +#include "base/chunk_generator.hh" +#include "base/cprintf.hh" // csprintf +#include "base/trace.hh" +#include "dev/disk_image.hh" +#include "dev/ide_disk.hh" +#include "dev/ide_ctrl.hh" +#include "dev/tsunami.hh" +#include "dev/tsunami_pchip.hh" +#include "sim/builder.hh" +#include "sim/sim_object.hh" +#include "sim/root.hh" +#include "arch/isa_traits.hh" + +using namespace std; +using namespace TheISA; + +IdeDisk::IdeDisk(const string &name, DiskImage *img, + int id, Tick delay) + : SimObject(name), ctrl(NULL), image(img), diskDelay(delay), + dmaTransferEvent(this), dmaReadCG(NULL), dmaReadWaitEvent(this), + dmaWriteCG(NULL), dmaWriteWaitEvent(this), dmaPrdReadEvent(this), + dmaReadEvent(this), dmaWriteEvent(this) +{ + // Reset the device state + reset(id); + + // 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 + 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 = 0x400; + // 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; + + // 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->plat->pciToDma(pciAddr); + else + panic("Access to unset controller!\n"); +} + +//// +// Device registers read/write +//// + +void +IdeDisk::read(const Addr &offset, IdeRegType reg_type, uint8_t *data) +{ + DevAction_t action = ACT_NONE; + + switch (reg_type) { + case COMMAND_BLOCK: + switch (offset) { + // Data transfers occur two bytes at a time + case DATA_OFFSET: + *(uint16_t*)data = cmdReg.data; + action = ACT_DATA_READ_SHORT; + break; + 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; + action = ACT_STAT_READ; + break; + default: + panic("Invalid IDE command register offset: %#x\n", offset); + } + break; + case CONTROL_BLOCK: + if (offset == ALTSTAT_OFFSET) + *data = status; + else + panic("Invalid IDE control register offset: %#x\n", offset); + break; + default: + panic("Unknown register block!\n"); + } + DPRINTF(IdeDisk, "Read to disk at offset: %#x data %#x\n", offset, + (uint32_t)*data); + + if (action != ACT_NONE) + updateState(action); +} + +void +IdeDisk::write(const Addr &offset, IdeRegType reg_type, const uint8_t *data) +{ + DevAction_t action = ACT_NONE; + + switch (reg_type) { + case COMMAND_BLOCK: + switch (offset) { + case DATA_OFFSET: + cmdReg.data = *(uint16_t*)data; + action = ACT_DATA_WRITE_SHORT; + break; + 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; + action = ACT_SELECT_WRITE; + break; + case COMMAND_OFFSET: + cmdReg.command = *data; + action = ACT_CMD_WRITE; + break; + default: + panic("Invalid IDE command register offset: %#x\n", offset); + } + break; + case CONTROL_BLOCK: + if (offset == CONTROL_OFFSET) { + if (*data & CONTROL_RST_BIT) { + // force the device into the reset state + devState = Device_Srst; + action = ACT_SRST_SET; + } else if (devState == Device_Srst && !(*data & CONTROL_RST_BIT)) + action = ACT_SRST_CLEAR; + + nIENBit = (*data & CONTROL_IEN_BIT) ? true : false; + } + else + panic("Invalid IDE control register offset: %#x\n", offset); + break; + default: + panic("Unknown register block!\n"); + } + + DPRINTF(IdeDisk, "Write to disk at offset: %#x data %#x\n", offset, + (uint32_t)*data); + if (action != ACT_NONE) + updateState(action); +} + +//// +// Perform DMA transactions +//// + +void +IdeDisk::doDmaTransfer() +{ + if (dmaState != Dma_Transfer || devState != Transfer_Data_Dma) + panic("Inconsistent DMA transfer state: dmaState = %d devState = %d\n", + dmaState, devState); + + if (ctrl->dmaPending()) { + dmaTransferEvent.schedule(curTick + DMA_BACKOFF_PERIOD); + return; + } else + ctrl->dmaRead(curPrdAddr, sizeof(PrdEntry_t), &dmaPrdReadEvent, + (uint8_t*)&curPrd.entry); +} + +void +IdeDisk::dmaPrdReadDone() +{ + 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); + + dmaReadWaitEvent.schedule(curTick + totalDiskDelay); +} + +void +IdeDisk::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 (!dmaReadCG) { + // clear out the data buffer + memset(dataBuffer, 0, MAX_DMA_SIZE); + dmaReadCG = new ChunkGenerator(curPrd.getBaseAddr(), + curPrd.getByteCount(), TheISA::PageBytes); + + } + if (ctrl->dmaPending()) { + panic("shouldn't be reentant??"); + dmaReadWaitEvent.schedule(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; + } + + dmaWriteWaitEvent.schedule(curTick + totalDiskDelay); +} + +void +IdeDisk::doDmaWrite() +{ + + if (!dmaWriteCG) { + // clear out the data buffer + dmaWriteCG = new ChunkGenerator(curPrd.getBaseAddr(), + curPrd.getByteCount(), TheISA::PageBytes); + } + if (ctrl->dmaPending()) { + panic("shouldn't be reentant??"); + dmaWriteWaitEvent.schedule(curTick + DMA_BACKOFF_PERIOD); + return; + } else if (!dmaWriteCG->done()) { + assert(dmaWriteCG->complete() < MAX_DMA_SIZE); + ctrl->dmaWrite(pciToDma(dmaWriteCG->addr()), dmaWriteCG->size(), + &dmaWriteWaitEvent, dataBuffer + dmaWriteCG->complete()); + dmaWriteBytes += dmaWriteCG->size(); + dmaWriteTxs++; + if (dmaWriteCG->size() == TheISA::PageBytes) + dmaWriteFullPages++; + dmaWriteCG->next(); + } else { + assert(dmaWriteCG->done()); + delete dmaWriteCG; + dmaWriteCG = NULL; + dmaWriteDone(); + } +} + +void +IdeDisk::dmaWriteDone() +{ + // 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) + dmaTransferEvent.schedule(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 = 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: + devState = Command_Execution; + action = ACT_CMD_COMPLETE; + break; + + // Supported PIO data-in commands + case WDCC_IDENTIFY: + 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); + + 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 + 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); + + 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->bmi_regs.bmis0 |= IDEINTS; + 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) { + // 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 || 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; + + default: + panic("Unknown IDE device state: %#x\n", devState); + } +} + +void +IdeDisk::serialize(ostream &os) +{ + // 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_ENUM(devState); + SERIALIZE_ENUM(dmaState); + SERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE); +} + +void +IdeDisk::unserialize(Checkpoint *cp, const string §ion) +{ + // 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 : dmaTransferEvent.schedule(reschedule); break; + case ReadWait : dmaReadWaitEvent.schedule(reschedule); break; + case WriteWait : dmaWriteWaitEvent.schedule(reschedule); break; + case PrdRead : dmaPrdReadEvent.schedule(reschedule); break; + case DmaRead : dmaReadEvent.schedule(reschedule); break; + case DmaWrite : dmaWriteEvent.schedule(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_ENUM(devState); + UNSERIALIZE_ENUM(dmaState); + UNSERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE); +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +enum DriveID { master, slave }; +static const char *DriveID_strings[] = { "master", "slave" }; +BEGIN_DECLARE_SIM_OBJECT_PARAMS(IdeDisk) + + SimObjectParam<DiskImage *> image; + SimpleEnumParam<DriveID> driveID; + Param<int> delay; + +END_DECLARE_SIM_OBJECT_PARAMS(IdeDisk) + +BEGIN_INIT_SIM_OBJECT_PARAMS(IdeDisk) + + INIT_PARAM(image, "Disk image"), + INIT_ENUM_PARAM(driveID, "Drive ID (0=master 1=slave)", DriveID_strings), + INIT_PARAM_DFLT(delay, "Fixed disk delay in microseconds", 1) + +END_INIT_SIM_OBJECT_PARAMS(IdeDisk) + + +CREATE_SIM_OBJECT(IdeDisk) +{ + return new IdeDisk(getInstanceName(), image, driveID, delay); +} + +REGISTER_SIM_OBJECT("IdeDisk", IdeDisk) + +#endif //DOXYGEN_SHOULD_SKIP_THIS |